From 30d52c3b264db32364e975d28a7fc24e250cb269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Fri, 21 Jun 2024 09:05:30 +0200 Subject: [PATCH 01/40] tests/e2e: use sed to update the chainspec and config --- nixos/default.nix | 2 +- nixos/tests/end-to-end.nix | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/nixos/default.nix b/nixos/default.nix index 15507470..8022b805 100644 --- a/nixos/default.nix +++ b/nixos/default.nix @@ -38,7 +38,7 @@ in inherit mkKairosHostConfig; inherit (self.packages.${pkgs.system}) kairos kairos-contracts; cctlModule = self.nixosModules.cctl; - inherit (inputs.csprpkgs.packages.${pkgs.system}) casper-client-rs; + inherit (inputs.csprpkgs.packages.${pkgs.system}) casper-client-rs casper-node; }; }; nixosModules = { diff --git a/nixos/tests/end-to-end.nix b/nixos/tests/end-to-end.nix index ed1c65b1..042e254e 100644 --- a/nixos/tests/end-to-end.nix +++ b/nixos/tests/end-to-end.nix @@ -4,8 +4,9 @@ , testResources ? ../../kairos-cli/tests/fixtures , kairos-contracts , cctlModule -, fetchurl , casper-client-rs +, casper-node +, runCommand }: nixosTest { name = "kairos e2e test"; @@ -28,14 +29,17 @@ nixosTest { services.cctl = { enable = true; - config = fetchurl { - url = "https://raw.githubusercontent.com/jonas089/casper-node/kairos-demo-chainspec/resources/local/config.toml"; - hash = "sha256-iwJkUY1dG+qh2BGXuwgnALJ3fNCvarr9ELJAqvd7kUg="; - }; - chainspec = fetchurl { - url = "https://raw.githubusercontent.com/jonas089/casper-node/kairos-demo-chainspec/resources/local/chainspec.toml.in"; - hash = "sha256-b/6c5o3JXFlaTgTHxs8JepaHzjMG75knzlKKqRd/7pc="; - }; + config = runCommand "patched-config" { } '' + sed -E "s/(max_body_bytes\s*=\s*)[0-9_]+/\118_388_608/" "${casper-node.src}/resources/local/config.toml" > "$out" + ''; + chainspec = runCommand "patched-chainspec" { } '' + sed -E "s/(max_block_size\s*=\s*)[0-9_]+/\1141_943_040/" "${casper-node.src}/resources/local/chainspec.toml.in" > "$out" + sed -i -E "s/(max_deploy_size\s*=\s*)[0-9_]+/\114_194_304/" "$out" + sed -i -E "s/(session_args_max_length\s*=\s*)[0-9_]+/\110000000/" "$out" + sed -i -E "s/(minimum_block_time\s*=\s*')[0-9]+ ms'/\1100 ms'/" "$out" + sed -i -E "s/(maximum_round_length\s*=\s*')[0-9]+ seconds'/\11 seconds'/" "$out" + + ''; }; services.kairos.casperRpcUrl = "http://localhost:${builtins.toString config.services.cctl.port}/rpc"; }; From f53992a8eef37125391a3fd41769c3643313d14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Fri, 21 Jun 2024 09:05:54 +0200 Subject: [PATCH 02/40] nixos/cctl: remove unneeded writableChainspec and writeableConfig --- nixos/modules/cctl.nix | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/nixos/modules/cctl.nix b/nixos/modules/cctl.nix index dbbe1653..2f184dd9 100644 --- a/nixos/modules/cctl.nix +++ b/nixos/modules/cctl.nix @@ -1,4 +1,4 @@ -{ lib, config, pkgs, ... }: +{ lib, config, ... }: let inherit (lib) types @@ -8,8 +8,6 @@ let mkEnableOption escapeShellArgs optionals - optional - concatLines ; cfg = config.services.cctl; in @@ -69,8 +67,6 @@ in systemd.services.cctl = let - writeableChainspec = "${cfg.workingDirectory}/chainspec.toml"; - writeableConfig = "${cfg.workingDirectory}/config.toml"; args = escapeShellArgs ([ "--working-dir" cfg.workingDirectory @@ -96,10 +92,6 @@ in serviceConfig = mkMerge [ { - ExecStartPre = - concatLines - ((optional (!builtins.isNull cfg.chainspec) "${pkgs.coreutils}/bin/cp --no-preserve=mode ${cfg.chainspec} ${writeableChainspec}") ++ - (optional (!builtins.isNull cfg.config) "${pkgs.coreutils}/bin/cp --no-preserve=mode ${cfg.config} ${writeableConfig}")); ExecStart = "${lib.getExe cfg.package} ${args}"; Type = "notify"; Restart = "no"; From 787d1090c4eeec9593408c0243a29eae394aabb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Fri, 21 Jun 2024 09:06:33 +0200 Subject: [PATCH 03/40] kairos-test-utils/cctl: use an exponential backoff waiting for validate --- kairos-test-utils/src/cctl.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/kairos-test-utils/src/cctl.rs b/kairos-test-utils/src/cctl.rs index 650614da..b005486a 100644 --- a/kairos-test-utils/src/cctl.rs +++ b/kairos-test-utils/src/cctl.rs @@ -1,6 +1,6 @@ pub mod parsers; use anyhow::anyhow; -use backoff::{backoff::Constant, future::retry}; +use backoff::{future::retry, ExponentialBackoff}; use casper_client::{get_node_status, rpcs::results::ReactorState, Error, JsonRpcId, Verbosity}; use std::io::{self, Write}; use std::path::Path; @@ -104,15 +104,8 @@ impl CCTLNetwork { .collect(); tracing::info!("Waiting for network to pass genesis"); - retry( - Constant::new(std::time::Duration::from_millis(100)), - || async { - let node_port = nodes.first().unwrap().port.rpc_port; - get_node_status( - JsonRpcId::Number(1), - &format!("http://localhost:{}", node_port), - Verbosity::High, - ) + retry(ExponentialBackoff::default(), || async { + get_node_status(JsonRpcId::Number(1), &casper_node_rpc_url, Verbosity::Low) .await .map_err(|err| match &err { Error::ResponseIsHttpError { .. } | Error::FailedToGetResponse { .. } => { @@ -126,8 +119,7 @@ impl CCTLNetwork { "Node didn't reach the VALIDATE state yet" ))), })? - }, - ) + }) .await .expect("Waiting for network to pass genesis failed"); From e1f5a1e140a8a0467c551201901ebe84ff45d1a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Sat, 22 Jun 2024 08:53:32 +0200 Subject: [PATCH 04/40] fixup missing casper_node_rpc_url --- kairos-test-utils/src/cctl.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kairos-test-utils/src/cctl.rs b/kairos-test-utils/src/cctl.rs index b005486a..467dc5c9 100644 --- a/kairos-test-utils/src/cctl.rs +++ b/kairos-test-utils/src/cctl.rs @@ -103,6 +103,9 @@ impl CCTLNetwork { }) .collect(); + let node_port = nodes.first().unwrap().port.rpc_port; + let casper_node_rpc_url = format!("http://localhost:{}/rpc", node_port); + tracing::info!("Waiting for network to pass genesis"); retry(ExponentialBackoff::default(), || async { get_node_status(JsonRpcId::Number(1), &casper_node_rpc_url, Verbosity::Low) From 41ec4fabb0ffeacaa3c9d89056ea23db59ea4794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 24 Jun 2024 10:48:48 +0200 Subject: [PATCH 05/40] Move `Signer::from_public_key()` out of `fs` feature. --- kairos-crypto/src/implementations/casper.rs | 32 ++++++++++----------- kairos-crypto/src/lib.rs | 6 ++-- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/kairos-crypto/src/implementations/casper.rs b/kairos-crypto/src/implementations/casper.rs index 4f01da98..8f895455 100644 --- a/kairos-crypto/src/implementations/casper.rs +++ b/kairos-crypto/src/implementations/casper.rs @@ -53,6 +53,21 @@ impl SignerCore for Signer { Ok(()) } + fn from_public_key>(bytes: T) -> Result + where + Self: Sized, + { + let (public_key, _remainder) = casper_types::PublicKey::from_bytes(bytes.as_ref()) + .map_err(|_e| CryptoError::Deserialization { + context: "public key", + })?; + + Ok(Self { + private_key: None, + public_key, + }) + } + fn to_public_key(&self) -> Result, CryptoError> { let public_key = self.public_key @@ -83,21 +98,6 @@ impl crate::SignerFsExtension for Signer { public_key, }) } - - fn from_public_key>(bytes: T) -> Result - where - Self: Sized, - { - let (public_key, _remainder) = casper_types::PublicKey::from_bytes(bytes.as_ref()) - .map_err(|_e| CryptoError::Deserialization { - context: "public key", - })?; - - Ok(Self { - private_key: None, - public_key, - }) - } } #[cfg(feature = "tx")] @@ -146,10 +146,8 @@ impl crate::SignerTxExtension for Signer { } #[cfg(test)] -#[cfg(feature = "fs")] mod tests { use super::*; - use crate::SignerFsExtension; #[test] fn test_casper_ed25519_public_key() { diff --git a/kairos-crypto/src/lib.rs b/kairos-crypto/src/lib.rs index 6691b215..06a2a26d 100644 --- a/kairos-crypto/src/lib.rs +++ b/kairos-crypto/src/lib.rs @@ -21,6 +21,9 @@ pub trait SignerCore { signature_bytes: U, ) -> Result<(), CryptoError>; + fn from_public_key>(bytes: T) -> Result + where + Self: Sized; fn to_public_key(&self) -> Result, CryptoError>; } @@ -29,9 +32,6 @@ pub trait SignerFsExtension: SignerCore { fn from_private_key_file>(file: P) -> Result where Self: Sized; - fn from_public_key>(bytes: T) -> Result - where - Self: Sized; } #[cfg(feature = "tx")] From 56e2f122440bf21868b174f85fc9f01ac3d61753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 24 Jun 2024 10:49:31 +0200 Subject: [PATCH 06/40] Disable default features for `kairos-tx` used in crypto module. This finally fixes `no_std` compatibility. --- kairos-crypto/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kairos-crypto/Cargo.toml b/kairos-crypto/Cargo.toml index d0499461..48ec4a38 100644 --- a/kairos-crypto/Cargo.toml +++ b/kairos-crypto/Cargo.toml @@ -15,7 +15,7 @@ fs = ["casper-types/std"] # FUTURE: Change `casper-types/std` -> `casper-types/s [dependencies] hex = { version = "0.4", default-features = false } -kairos-tx = { path = "../kairos-tx", optional = true } +kairos-tx = { path = "../kairos-tx", default-features = false, optional = true } # Casper signer implementation. casper-types = { workspace = true, optional = true } From e15bdf4baa9e8398853967a02d7eb69a14f0d4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Tue, 25 Jun 2024 08:21:44 +0200 Subject: [PATCH 07/40] kairos-server/config: make the contract hash typed --- Cargo.lock | 2 ++ kairos-cli/tests/cli_tests.rs | 2 +- kairos-server/.env | 2 +- kairos-server/Cargo.toml | 1 + kairos-server/src/config.rs | 17 ++++++++++++++--- kairos-server/src/l1_sync/event_manager.rs | 7 +++++-- kairos-server/src/lib.rs | 5 +++-- kairos-server/tests/transactions.rs | 4 ++-- kairos-test-utils/Cargo.toml | 1 + kairos-test-utils/src/kairos.rs | 8 ++++---- nixos/configurations/kairos-host/default.nix | 5 ++++- nixos/modules/kairos.nix | 16 ++++++++++++---- 12 files changed, 50 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c0476f3..3e91f0df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2506,6 +2506,7 @@ dependencies = [ "casper-client", "casper-event-toolkit", "casper-types 3.0.0", + "casper-types 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "contract-utils", "dotenvy", "hex", @@ -2532,6 +2533,7 @@ dependencies = [ "anyhow", "backoff", "casper-client", + "casper-types 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "clap", "dotenvy", "kairos-server", diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index 5e3b6f7b..8100fde4 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -25,7 +25,7 @@ async fn deposit_successful_with_ed25519() { .expect("Expected at least one node after successful network run"); let node_url = Url::parse(&format!("http://localhost:{}/rpc", node.port.rpc_port)).unwrap(); - let kairos = kairos::Kairos::run(node_url, None).await.unwrap(); + let kairos = kairos::Kairos::run(node_url, None, None).await.unwrap(); tokio::task::spawn_blocking(move || { let depositor_secret_key_path = network diff --git a/kairos-server/.env b/kairos-server/.env index 9c37acae..256ff991 100644 --- a/kairos-server/.env +++ b/kairos-server/.env @@ -1,3 +1,3 @@ KAIROS_SERVER_SOCKET_ADDR="127.0.0.1:7893" KAIROS_SERVER_CASPER_RPC="http://127.0.0.1:11101/rpc" -KAIROS_SERVER_CASPER_CONTRACT_HASH="0000000000000000000000000000000000000000000000000000000000000000" +KAIROS_SERVER_DEMO_CONTRACT_HASH="0000000000000000000000000000000000000000000000000000000000000000" diff --git a/kairos-server/Cargo.toml b/kairos-server/Cargo.toml index f12a8cc4..133502ef 100644 --- a/kairos-server/Cargo.toml +++ b/kairos-server/Cargo.toml @@ -27,6 +27,7 @@ axum-extra = { version = "0.9", features = [ ] } anyhow = "1" casper-client.workspace = true +casper-types.workspace = true rand = "0.8" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/kairos-server/src/config.rs b/kairos-server/src/config.rs index b0952370..95bd98ca 100644 --- a/kairos-server/src/config.rs +++ b/kairos-server/src/config.rs @@ -1,3 +1,5 @@ +use casper_types::ContractHash; +use hex::FromHex; use reqwest::Url; use std::net::SocketAddr; use std::time::Duration; @@ -7,7 +9,7 @@ use std::{fmt, str::FromStr}; pub struct ServerConfig { pub socket_addr: SocketAddr, pub casper_rpc: Url, - pub casper_contract_hash: String, + pub kairos_demo_contract_hash: ContractHash, pub batch_config: BatchConfig, } @@ -16,12 +18,21 @@ impl ServerConfig { let socket_addr = parse_env_as::("KAIROS_SERVER_SOCKET_ADDR")?; let casper_rpc = parse_env_as::("KAIROS_SERVER_CASPER_RPC")?; let batch_config = BatchConfig::from_env()?; - let casper_contract_hash = parse_env_as::("KAIROS_SERVER_CASPER_CONTRACT_HASH")?; + let kairos_demo_contract_hash = parse_env_as::("KAIROS_SERVER_DEMO_CONTRACT_HASH") + .and_then(|contract_hash_string| { + <[u8; 32]>::from_hex(&contract_hash_string).map_err(|err| { + panic!( + "Failed to decode kairos-demo-contract-hash {}: {}", + contract_hash_string, err + ) + }) + }) + .map(ContractHash::new)?; Ok(Self { socket_addr, casper_rpc, - casper_contract_hash, + kairos_demo_contract_hash, batch_config, }) } diff --git a/kairos-server/src/l1_sync/event_manager.rs b/kairos-server/src/l1_sync/event_manager.rs index 17a3b27f..e42acd9e 100644 --- a/kairos-server/src/l1_sync/event_manager.rs +++ b/kairos-server/src/l1_sync/event_manager.rs @@ -23,9 +23,12 @@ impl EventManager { tracing::info!("Initializing event manager"); let rpc_url = server_state.server_config.casper_rpc.as_str(); - let contract_hash = server_state.server_config.casper_contract_hash.as_str(); + let contract_hash = server_state + .server_config + .kairos_demo_contract_hash + .to_string(); let client = CasperClient::new(rpc_url); - let metadata = CesMetadataRef::fetch_metadata(&client, contract_hash).await?; + let metadata = CesMetadataRef::fetch_metadata(&client, &contract_hash).await?; tracing::debug!("Metadata fetched successfully"); let fetcher = Fetcher { diff --git a/kairos-server/src/lib.rs b/kairos-server/src/lib.rs index 44ce19f4..c10686bb 100644 --- a/kairos-server/src/lib.rs +++ b/kairos-server/src/lib.rs @@ -10,6 +10,7 @@ use std::sync::Arc; use axum::Router; use axum_extra::routing::RouterExt; +use casper_types::ContractHash; pub use errors::AppErr; @@ -42,8 +43,8 @@ pub fn app_router(state: ServerState) -> Router { pub async fn run_l1_sync(server_state: Arc) { // Extra check: make sure the default dummy value of contract hash was changed. - let contract_hash = server_state.server_config.casper_contract_hash.as_str(); - if contract_hash == "0000000000000000000000000000000000000000000000000000000000000000" { + let contract_hash = server_state.server_config.kairos_demo_contract_hash; + if contract_hash == ContractHash::default() { tracing::warn!( "Casper contract hash not configured, L1 synchronization will NOT be enabled." ); diff --git a/kairos-server/tests/transactions.rs b/kairos-server/tests/transactions.rs index fe6613c3..8deb7e51 100644 --- a/kairos-server/tests/transactions.rs +++ b/kairos-server/tests/transactions.rs @@ -10,6 +10,7 @@ use casper_client_types::{ crypto::{PublicKey, SecretKey}, AsymmetricType, }; +use casper_types::ContractHash; use kairos_server::{ config::{BatchConfig, ServerConfig}, routes::deposit::DepositPath, @@ -47,8 +48,7 @@ fn new_test_app_with_casper_node(casper_node_url: &Url) -> TestServer { let server_config = ServerConfig { socket_addr: "0.0.0.0:0".parse().unwrap(), casper_rpc: casper_node_url.clone(), - casper_contract_hash: "0000000000000000000000000000000000000000000000000000000000000000" - .to_string(), + kairos_demo_contract_hash: ContractHash::default(), batch_config: BatchConfig { max_batch_size: None, max_batch_duration: None, diff --git a/kairos-test-utils/Cargo.toml b/kairos-test-utils/Cargo.toml index 75597cde..19f23edc 100644 --- a/kairos-test-utils/Cargo.toml +++ b/kairos-test-utils/Cargo.toml @@ -23,6 +23,7 @@ anyhow = "1" backoff = { version = "0.4", features = ["tokio", "futures"]} clap = { version = "4", features = ["derive"] } casper-client.workspace = true +casper-types.workspace = true nom = "7" sd-notify = "0.4" tokio = { version = "1", features = [ "full", "tracing", "macros" ] } diff --git a/kairos-test-utils/src/kairos.rs b/kairos-test-utils/src/kairos.rs index ce9e6d0f..845d2b2f 100644 --- a/kairos-test-utils/src/kairos.rs +++ b/kairos-test-utils/src/kairos.rs @@ -1,5 +1,6 @@ use backoff::future::retry; use backoff::ExponentialBackoff; +use casper_types::ContractHash; use reqwest::Url; use std::io; use std::net::{SocketAddr, TcpListener}; @@ -26,6 +27,7 @@ impl Kairos { pub async fn run( casper_rpc: Url, proving_server_batch_config: Option, + kairos_demo_contract_hash: Option, ) -> Result { let socket_addr = TcpListener::bind("0.0.0.0:0")?.local_addr()?; let port = socket_addr.port().to_string(); @@ -42,9 +44,7 @@ impl Kairos { let config = ServerConfig { socket_addr, casper_rpc, - casper_contract_hash: String::from( - "0000000000000000000000000000000000000000000000000000000000000000", - ), + kairos_demo_contract_hash: kairos_demo_contract_hash.unwrap_or_default(), batch_config, }; @@ -116,6 +116,6 @@ mod tests { #[tokio::test] async fn test_kairos_starts_and_terminates() { let dummy_rpc = Url::parse("http://127.0.0.1:11101/rpc").unwrap(); - let _kairos = Kairos::run(dummy_rpc, None).await.unwrap(); + let _kairos = Kairos::run(dummy_rpc, None, None).await.unwrap(); } } diff --git a/nixos/configurations/kairos-host/default.nix b/nixos/configurations/kairos-host/default.nix index a56a8729..d60f2edf 100644 --- a/nixos/configurations/kairos-host/default.nix +++ b/nixos/configurations/kairos-host/default.nix @@ -22,5 +22,8 @@ }; }; - services.kairos.enable = true; + services.kairos = { + enable = true; + demoContractHash = "0000000000000000000000000000000000000000000000000000000000000000"; + }; } diff --git a/nixos/modules/kairos.nix b/nixos/modules/kairos.nix index 3546ea0c..3f0f06a2 100644 --- a/nixos/modules/kairos.nix +++ b/nixos/modules/kairos.nix @@ -15,7 +15,6 @@ in options.services.kairos = { enable = mkEnableOption (mdDoc "kairos"); - package = mkOption { type = types.package; }; @@ -42,7 +41,16 @@ in type = types.str; example = "http://127.0.0.1:11101/rpc"; description = '' - A casper node URL. + The casper node URL to the RPC endpoint. + ''; + }; + + demoContractHash = mkOption { + type = types.str; + example = "TODO put a contract hash here"; + description = '' + The hash of the deployed demo contract. + Use an empty string when testing with cctl. ''; }; @@ -124,8 +132,8 @@ in environment = { RUST_LOG = cfg.logLevel; KAIROS_SERVER_SOCKET_ADDR = "${cfg.bindAddress}:${builtins.toString cfg.port}"; - KAIROS_SERVER_CASPER_RPC = "${cfg.casperRpcUrl}"; - KAIROS_SERVER_CASPER_CONTRACT_HASH = "0000000000000000000000000000000000000000000000000000000000000000"; + KAIROS_SERVER_CASPER_RPC = cfg.casperRpcUrl; + 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) { KAIROS_SERVER_MAX_BATCH_SIZE = cfg.maxBatchSize; From 6980e763cec0e68eac8abe9bc1f9ac836f219a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Tue, 25 Jun 2024 09:01:34 +0200 Subject: [PATCH 08/40] kairos-server: add casper sse url --- kairos-cli/tests/cli_tests.rs | 12 ++++++++++-- kairos-server/.env | 1 + kairos-server/src/config.rs | 3 +++ kairos-server/tests/transactions.rs | 17 ++++++++++++----- kairos-test-utils/src/kairos.rs | 9 ++++++--- nixos/default.nix | 7 +++++-- nixos/modules/kairos.nix | 11 ++++++++++- nixos/tests/end-to-end.nix | 5 ++++- 8 files changed, 51 insertions(+), 14 deletions(-) diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index 5e3b6f7b..bfc87241 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -23,9 +23,17 @@ async fn deposit_successful_with_ed25519() { .nodes .first() .expect("Expected at least one node after successful network run"); - let node_url = Url::parse(&format!("http://localhost:{}/rpc", node.port.rpc_port)).unwrap(); + let casper_rpc_url = + Url::parse(&format!("http://localhost:{}/rpc", node.port.rpc_port)).unwrap(); + let casper_sse_url = Url::parse(&format!( + "http://localhost:{}/events/main", + node.port.sse_port + )) + .unwrap(); - let kairos = kairos::Kairos::run(node_url, None).await.unwrap(); + let kairos = kairos::Kairos::run(&casper_rpc_url, &casper_sse_url, None) + .await + .unwrap(); tokio::task::spawn_blocking(move || { let depositor_secret_key_path = network diff --git a/kairos-server/.env b/kairos-server/.env index 9c37acae..f1504ead 100644 --- a/kairos-server/.env +++ b/kairos-server/.env @@ -1,3 +1,4 @@ KAIROS_SERVER_SOCKET_ADDR="127.0.0.1:7893" 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_CONTRACT_HASH="0000000000000000000000000000000000000000000000000000000000000000" diff --git a/kairos-server/src/config.rs b/kairos-server/src/config.rs index b0952370..27fbdd93 100644 --- a/kairos-server/src/config.rs +++ b/kairos-server/src/config.rs @@ -7,6 +7,7 @@ use std::{fmt, str::FromStr}; pub struct ServerConfig { pub socket_addr: SocketAddr, pub casper_rpc: Url, + pub casper_sse: Url, pub casper_contract_hash: String, pub batch_config: BatchConfig, } @@ -15,12 +16,14 @@ 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 batch_config = BatchConfig::from_env()?; let casper_contract_hash = parse_env_as::("KAIROS_SERVER_CASPER_CONTRACT_HASH")?; Ok(Self { socket_addr, casper_rpc, + casper_sse, casper_contract_hash, batch_config, }) diff --git a/kairos-server/tests/transactions.rs b/kairos-server/tests/transactions.rs index fe6613c3..c8895183 100644 --- a/kairos-server/tests/transactions.rs +++ b/kairos-server/tests/transactions.rs @@ -30,10 +30,11 @@ static TEST_ENVIRONMENT: OnceLock<()> = OnceLock::new(); #[cfg(feature = "deposit-mock")] fn new_test_app() -> TestServer { - new_test_app_with_casper_node(&Url::parse("http://0.0.0.0:0").unwrap()) + let dummy_url = Url::parse("http://0.0.0.0:0").unwrap(); + new_test_app_with_casper_node(&dummy_url, &dummy_url) } -fn new_test_app_with_casper_node(casper_node_url: &Url) -> TestServer { +fn new_test_app_with_casper_node(casper_rpc_url: &Url, casper_sse_url: &Url) -> TestServer { TEST_ENVIRONMENT.get_or_init(|| { tracing_subscriber::registry() .with( @@ -46,7 +47,8 @@ fn new_test_app_with_casper_node(casper_node_url: &Url) -> TestServer { let config = TestServerConfig::builder().mock_transport().build(); let server_config = ServerConfig { socket_addr: "0.0.0.0:0".parse().unwrap(), - casper_rpc: casper_node_url.clone(), + casper_rpc: casper_rpc_url.clone(), + casper_sse: casper_sse_url.clone(), casper_contract_hash: "0000000000000000000000000000000000000000000000000000000000000000" .to_string(), batch_config: BatchConfig { @@ -75,10 +77,15 @@ async fn test_signed_deploy_is_forwarded_if_sender_in_approvals() { .nodes .first() .expect("Expected at least one node after successful network run"); - let casper_node_url = + let casper_rpc_url = Url::parse(&format!("http://localhost:{}/rpc", node.port.rpc_port)).unwrap(); + let casper_sse_url = Url::parse(&format!( + "http://localhost:{}/events/main", + node.port.sse_port + )) + .unwrap(); - let server = new_test_app_with_casper_node(&casper_node_url); + let server = new_test_app_with_casper_node(&casper_rpc_url, &casper_sse_url); let sender_secret_key_file = network .working_dir diff --git a/kairos-test-utils/src/kairos.rs b/kairos-test-utils/src/kairos.rs index ce9e6d0f..9e841fbc 100644 --- a/kairos-test-utils/src/kairos.rs +++ b/kairos-test-utils/src/kairos.rs @@ -24,7 +24,8 @@ impl Kairos { /// If no proving server is running, we will start the one at `BatchConfig.proving_server`. /// The caller should ensure that `BatchConfig.proving_server == KAIROS_PROVER_SERVER_URL`. pub async fn run( - casper_rpc: Url, + casper_rpc: &Url, + casper_sse: &Url, proving_server_batch_config: Option, ) -> Result { let socket_addr = TcpListener::bind("0.0.0.0:0")?.local_addr()?; @@ -41,7 +42,8 @@ impl Kairos { let config = ServerConfig { socket_addr, - casper_rpc, + casper_rpc: casper_rpc.clone(), + casper_sse: casper_sse.clone(), casper_contract_hash: String::from( "0000000000000000000000000000000000000000000000000000000000000000", ), @@ -116,6 +118,7 @@ mod tests { #[tokio::test] async fn test_kairos_starts_and_terminates() { let dummy_rpc = Url::parse("http://127.0.0.1:11101/rpc").unwrap(); - let _kairos = Kairos::run(dummy_rpc, None).await.unwrap(); + let dummy_sse = Url::parse("http://127.0.0.1:18101/events/main").unwrap(); + let _kairos = Kairos::run(&dummy_rpc, &dummy_sse, None).await.unwrap(); } } diff --git a/nixos/default.nix b/nixos/default.nix index ddfc52d9..ad6c74e8 100644 --- a/nixos/default.nix +++ b/nixos/default.nix @@ -35,8 +35,11 @@ in imports = [ (mkKairosHostConfig "kairos-host") ]; - # A placeholder URL to make the test pass - services.kairos.casperRpcUrl = "http://localhost:11101/rpc"; + # Placeholder values make the test pass + services.kairos = { + casperRpcUrl = "http://localhost:11101/rpc"; + casperSseUrl = "http://localhost:18101/events/main"; + }; }; verifyServices = [ "kairos.service" ]; }; diff --git a/nixos/modules/kairos.nix b/nixos/modules/kairos.nix index 3546ea0c..b6f60440 100644 --- a/nixos/modules/kairos.nix +++ b/nixos/modules/kairos.nix @@ -46,6 +46,14 @@ in ''; }; + casperSseUrl = mkOption { + type = types.str; + example = "http://127.0.0.1:18101/events/main"; + description = '' + The casper node URL to the SSE events endpoint. + ''; + }; + prover = mkOption { description = "Prover server related options"; default = { }; @@ -124,7 +132,8 @@ in environment = { RUST_LOG = cfg.logLevel; KAIROS_SERVER_SOCKET_ADDR = "${cfg.bindAddress}:${builtins.toString cfg.port}"; - KAIROS_SERVER_CASPER_RPC = "${cfg.casperRpcUrl}"; + KAIROS_SERVER_CASPER_RPC = cfg.casperRpcUrl; + KAIROS_SERVER_CASPER_SSE = cfg.casperSseUrl; KAIROS_SERVER_CASPER_CONTRACT_HASH = "0000000000000000000000000000000000000000000000000000000000000000"; 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 db982202..14836294 100644 --- a/nixos/tests/end-to-end.nix +++ b/nixos/tests/end-to-end.nix @@ -37,7 +37,10 @@ nixosTest { hash = "sha256-ZuNbxw0nBjuONEZRK8Ru96zZQak4MEQ/eM1fA6esyCM="; }; }; - services.kairos.casperRpcUrl = "http://localhost:${builtins.toString config.services.cctl.port}/rpc"; + 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 + }; }; client = { pkgs, ... }: { From 358b8bd8aeec7be0b2294cc84fd29341e5fbafb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Tue, 25 Jun 2024 09:31:33 +0200 Subject: [PATCH 09/40] tests/e2e: revert back to use the same chainspec as in contract tests --- nixos/default.nix | 1 - nixos/tests/end-to-end.nix | 22 +++++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/nixos/default.nix b/nixos/default.nix index c946dd0c..ddfc52d9 100644 --- a/nixos/default.nix +++ b/nixos/default.nix @@ -47,7 +47,6 @@ in inherit mkKairosHostConfig; inherit (self.packages.${pkgs.system}) kairos kairos-contracts casper-client-rs; cctlModule = self.nixosModules.cctl; - inherit (inputs.csprpkgs.packages.${pkgs.system}) casper-node; }; }; nixosModules = { diff --git a/nixos/tests/end-to-end.nix b/nixos/tests/end-to-end.nix index 042e254e..33165337 100644 --- a/nixos/tests/end-to-end.nix +++ b/nixos/tests/end-to-end.nix @@ -5,8 +5,7 @@ , kairos-contracts , cctlModule , casper-client-rs -, casper-node -, runCommand +, fetchurl }: nixosTest { name = "kairos e2e test"; @@ -29,17 +28,14 @@ nixosTest { services.cctl = { enable = true; - config = runCommand "patched-config" { } '' - sed -E "s/(max_body_bytes\s*=\s*)[0-9_]+/\118_388_608/" "${casper-node.src}/resources/local/config.toml" > "$out" - ''; - chainspec = runCommand "patched-chainspec" { } '' - sed -E "s/(max_block_size\s*=\s*)[0-9_]+/\1141_943_040/" "${casper-node.src}/resources/local/chainspec.toml.in" > "$out" - sed -i -E "s/(max_deploy_size\s*=\s*)[0-9_]+/\114_194_304/" "$out" - sed -i -E "s/(session_args_max_length\s*=\s*)[0-9_]+/\110000000/" "$out" - sed -i -E "s/(minimum_block_time\s*=\s*')[0-9]+ ms'/\1100 ms'/" "$out" - sed -i -E "s/(maximum_round_length\s*=\s*')[0-9]+ seconds'/\11 seconds'/" "$out" - - ''; + chainspec = fetchurl { + url = "https://raw.githubusercontent.com/cspr-rad/casper-node/53136ac5f004f2ae70a75b4eeb2ff7d907aff6aa/resources/local/chainspec.toml.in"; + hash = "sha256-b/6c5o3JXFlaTgTHxs8JepaHzjMG75knzlKKqRd/7pc="; + }; + config = fetchurl { + url = "https://raw.githubusercontent.com/cspr-rad/casper-node/53136ac5f004f2ae70a75b4eeb2ff7d907aff6aa/resources/local/config.toml"; + hash = "sha256-ZuNbxw0nBjuONEZRK8Ru96zZQak4MEQ/eM1fA6esyCM="; + }; }; services.kairos.casperRpcUrl = "http://localhost:${builtins.toString config.services.cctl.port}/rpc"; }; From 3e9654864be2531cf019defaa2a6e388aa449729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Tue, 25 Jun 2024 12:42:31 +0200 Subject: [PATCH 10/40] kairos-contracts/contract-utils: remove unused casper-types dependency --- Cargo.lock | 1 - kairos-contracts/Cargo.lock | 1 - kairos-contracts/demo-contract/contract-utils/Cargo.toml | 1 - 3 files changed, 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c0476f3..bbe75897 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1203,7 +1203,6 @@ name = "contract-utils" version = "0.1.0" dependencies = [ "casper-event-standard", - "casper-types 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/kairos-contracts/Cargo.lock b/kairos-contracts/Cargo.lock index d147125d..d5cd73d3 100644 --- a/kairos-contracts/Cargo.lock +++ b/kairos-contracts/Cargo.lock @@ -431,7 +431,6 @@ name = "contract-utils" version = "0.1.0" dependencies = [ "casper-event-standard", - "casper-types", ] [[package]] diff --git a/kairos-contracts/demo-contract/contract-utils/Cargo.toml b/kairos-contracts/demo-contract/contract-utils/Cargo.toml index c26169c7..dca95764 100644 --- a/kairos-contracts/demo-contract/contract-utils/Cargo.toml +++ b/kairos-contracts/demo-contract/contract-utils/Cargo.toml @@ -10,5 +10,4 @@ bench = false doctest = false [dependencies] -casper-types.workspace = true casper-event-standard = { workspace = true, default-features = false } From 32af194088bd4a637c1812f207d7f2b966b3a21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Tue, 25 Jun 2024 12:43:42 +0200 Subject: [PATCH 11/40] kairos-contracts/demo-contract: use u32 for the last processed deposit counter --- kairos-contracts/demo-contract/contract/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kairos-contracts/demo-contract/contract/src/main.rs b/kairos-contracts/demo-contract/contract/src/main.rs index a0435d5d..e1558b1b 100644 --- a/kairos-contracts/demo-contract/contract/src/main.rs +++ b/kairos-contracts/demo-contract/contract/src/main.rs @@ -136,7 +136,7 @@ pub extern "C" fn call() { ]); // this counter will be udpated by the entry point that processes / verifies batches - let last_processed_deposit_counter_uref: URef = storage::new_uref(0u64); + let last_processed_deposit_counter_uref: URef = storage::new_uref(0u32); let initial_trie_root: Option<[u8; 32]> = runtime::get_named_arg(RUNTIME_ARG_INITIAL_TRIE_ROOT); From 7755159ce518dce5a3297954ba3c0112092dbd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Tue, 25 Jun 2024 07:58:35 +0200 Subject: [PATCH 12/40] kairos-test-utils/cctl: add deploy contract function --- Cargo.lock | 4 + kairos-cli/tests/cli_tests.rs | 2 +- kairos-server/tests/transactions.rs | 4 +- kairos-test-utils/Cargo.toml | 8 +- kairos-test-utils/bin/cctld.rs | 17 ++ kairos-test-utils/src/cctl.rs | 272 ++++++++++++++++++++++++++-- nixos/modules/cctl.nix | 40 ++-- 7 files changed, 310 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c0476f3..ec4b4b23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2532,10 +2532,14 @@ dependencies = [ "anyhow", "backoff", "casper-client", + "casper-types 3.0.0", + "casper-types 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "clap", "dotenvy", + "hex", "kairos-server", "nom", + "rand 0.8.5", "reqwest 0.12.4", "sd-notify", "tempfile", diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index 5e3b6f7b..fe9c6f2c 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -16,7 +16,7 @@ fn fixture_path(relative_path: &str) -> PathBuf { #[tokio::test] #[cfg_attr(not(feature = "cctl-tests"), ignore)] async fn deposit_successful_with_ed25519() { - let network = cctl::CCTLNetwork::run(Option::None, Option::None, Option::None) + let network = cctl::CCTLNetwork::run(None, None, None, None) .await .unwrap(); let node = network diff --git a/kairos-server/tests/transactions.rs b/kairos-server/tests/transactions.rs index fe6613c3..c3ac17ed 100644 --- a/kairos-server/tests/transactions.rs +++ b/kairos-server/tests/transactions.rs @@ -68,9 +68,7 @@ fn new_test_app_with_casper_node(casper_node_url: &Url) -> TestServer { #[tokio::test] #[cfg_attr(not(feature = "cctl-tests"), ignore)] async fn test_signed_deploy_is_forwarded_if_sender_in_approvals() { - let network = CCTLNetwork::run(Option::None, Option::None, Option::None) - .await - .unwrap(); + let network = CCTLNetwork::run(None, None, None, None).await.unwrap(); let node = network .nodes .first() diff --git a/kairos-test-utils/Cargo.toml b/kairos-test-utils/Cargo.toml index 75597cde..664d72bd 100644 --- a/kairos-test-utils/Cargo.toml +++ b/kairos-test-utils/Cargo.toml @@ -12,7 +12,9 @@ test = false bench = false [features] -all-tests = ["cctl-tests"] +# FIXME enable cctl-tests once this crate is factored out in a separate repository +#all-tests = ["cctl-tests"] +all-tests = [] cctl-tests = [] [lib] @@ -23,7 +25,11 @@ anyhow = "1" backoff = { version = "0.4", features = ["tokio", "futures"]} clap = { version = "4", features = ["derive"] } casper-client.workspace = true +casper-types.workspace = true +casper-client-types.workspace = true nom = "7" +hex = "0.4" +rand = "0.8" sd-notify = "0.4" tokio = { version = "1", features = [ "full", "tracing", "macros" ] } tempfile = "3" diff --git a/kairos-test-utils/bin/cctld.rs b/kairos-test-utils/bin/cctld.rs index be916c0a..04362149 100644 --- a/kairos-test-utils/bin/cctld.rs +++ b/kairos-test-utils/bin/cctld.rs @@ -1,14 +1,19 @@ +use casper_client_types::{runtime_args, RuntimeArgs}; use clap::Parser; use kairos_test_utils::cctl; use sd_notify::NotifyState; use std::path::PathBuf; use tokio::signal; +use crate::cctl::DeployableContract; + #[derive(Parser)] pub struct Cli { #[arg(short, long)] pub working_dir: Option, #[arg(short, long)] + pub deploy_contract: Option, + #[arg(short, long)] pub chainspec_path: Option, #[arg(short, long)] pub config_path: Option, @@ -17,8 +22,20 @@ pub struct Cli { #[tokio::main] async fn main() -> Result<(), Box> { let cli = Cli::parse(); + let deploy_contract = cli.deploy_contract.map(|deploy_contracts_arg| { + match deploy_contracts_arg.split_once(':') { + Some((hash_name, path)) => DeployableContract { + hash_name: hash_name.to_string(), + // FIXME at some point we want to make this parametrizable + runtime_args: runtime_args! { "initial_trie_root" => Option::<[u8; 32]>::None }, + path: PathBuf::from(&path), + }, + None => panic!("Error parsing the provided deploy contracts argument."), + } + }); let _network = cctl::CCTLNetwork::run( cli.working_dir, + deploy_contract, cli.chainspec_path.as_deref(), cli.config_path.as_deref(), ) diff --git a/kairos-test-utils/src/cctl.rs b/kairos-test-utils/src/cctl.rs index 650614da..3f8b52a9 100644 --- a/kairos-test-utils/src/cctl.rs +++ b/kairos-test-utils/src/cctl.rs @@ -1,7 +1,17 @@ pub mod parsers; use anyhow::anyhow; -use backoff::{backoff::Constant, future::retry}; -use casper_client::{get_node_status, rpcs::results::ReactorState, Error, JsonRpcId, Verbosity}; +use backoff::{future::retry, ExponentialBackoff}; +use casper_client::{ + get_account, get_deploy, get_node_status, get_state_root_hash, put_deploy, query_global_state, + rpcs::results::ReactorState, + types::{DeployBuilder, ExecutableDeployItem, StoredValue, TimeDiff, Timestamp}, + Error, JsonRpcId, Verbosity, +}; +use casper_client_types::{ExecutionResult, Key, PublicKey, RuntimeArgs, SecretKey}; +use casper_types::ContractHash; +use hex::FromHex; +use rand::Rng; +use std::fs; use std::io::{self, Write}; use std::path::Path; use std::path::PathBuf; @@ -35,12 +45,23 @@ pub struct CCTLNetwork { pub nodes: Vec, } +pub struct DeployableContract { + /// This is the named key under which the contract hash is located + pub hash_name: String, + pub runtime_args: RuntimeArgs, + pub path: PathBuf, +} + +// max amount allowed to be used on gas fees +pub const MAX_GAS_FEE_PAYMENT_AMOUNT: u64 = 10_000_000_000_000; + impl CCTLNetwork { pub async fn run( working_dir: Option, + contract_to_deploy: Option, chainspec_path: Option<&Path>, config_path: Option<&Path>, - ) -> Result { + ) -> anyhow::Result { let working_dir = working_dir .map(|dir| { std::fs::create_dir_all(&dir) @@ -103,16 +124,12 @@ impl CCTLNetwork { }) .collect(); + let node_port = nodes.first().unwrap().port.rpc_port; + let casper_node_rpc_url = format!("http://localhost:{}/rpc", node_port); + tracing::info!("Waiting for network to pass genesis"); - retry( - Constant::new(std::time::Duration::from_millis(100)), - || async { - let node_port = nodes.first().unwrap().port.rpc_port; - get_node_status( - JsonRpcId::Number(1), - &format!("http://localhost:{}", node_port), - Verbosity::High, - ) + retry(ExponentialBackoff::default(), || async { + get_node_status(JsonRpcId::Number(1), &casper_node_rpc_url, Verbosity::Low) .await .map_err(|err| match &err { Error::ResponseIsHttpError { .. } | Error::FailedToGetResponse { .. } => { @@ -126,8 +143,7 @@ impl CCTLNetwork { "Node didn't reach the VALIDATE state yet" ))), })? - }, - ) + }) .await .expect("Waiting for network to pass genesis failed"); @@ -140,8 +156,40 @@ impl CCTLNetwork { let output = std::str::from_utf8(output.stdout.as_slice()).unwrap(); tracing::info!("{}", output); + if let Some(contract_to_deploy) = contract_to_deploy { + let deployer_skey = + SecretKey::from_file(working_dir.join("assets/users/user-1/secret_key.pem"))?; + let deployer_pkey = + PublicKey::from_file(working_dir.join("assets/users/user-1/public_key.pem"))?; + + let (hash_name, contract_hash) = deploy_contract( + &casper_node_rpc_url, + &deployer_skey, + &deployer_pkey, + &contract_to_deploy, + ) + .await?; + let contracts_dir = working_dir.join("contracts"); + fs::create_dir_all(&contracts_dir)?; + fs::write( + contracts_dir.join(hash_name), + // For a ContractHash contract- will always be the prefix + contract_hash + .to_formatted_string() + .strip_prefix("contract-") + .unwrap(), + )? + } Ok(CCTLNetwork { working_dir, nodes }) } + /// Get the deployed contract hash for a hash_name that was passed to new_contract + /// https://docs.rs/casper-contract/latest/casper_contract/contract_api/storage/fn.new_contract.html + pub fn get_contract_hash_for(&self, hash_name: &str) -> ContractHash { + let contract_hash_path = self.working_dir.join("contracts").join(hash_name); + let contract_hash_string = fs::read_to_string(contract_hash_path).unwrap(); + let contract_hash_bytes = <[u8; 32]>::from_hex(contract_hash_string).unwrap(); + ContractHash::new(contract_hash_bytes) + } } impl Drop for CCTLNetwork { @@ -155,15 +203,182 @@ impl Drop for CCTLNetwork { } } +/// Deploys a contract as the given user for the contract's defined hash name located at the path. +/// The hash name should be equal to the hash name passed to https://docs.rs/casper-contract/latest/casper_contract/contract_api/storage/fn.new_locked_contract.html +async fn deploy_contract( + casper_node_rpc_url: &str, + contract_deployer_skey: &SecretKey, + contract_deployer_pkey: &PublicKey, + DeployableContract { + hash_name, + runtime_args, + path, + }: &DeployableContract, +) -> anyhow::Result<(String, casper_client_types::ContractHash)> { + tracing::info!( + "Deploying contract {}: {}", + &hash_name, + path.to_str().unwrap() + ); + + let contract_bytes = fs::read(path)?; + let contract = + ExecutableDeployItem::new_module_bytes(contract_bytes.into(), runtime_args.clone()); + let deploy = DeployBuilder::new( + // TODO ideally make the chain-name this configurable + "cspr-dev-cctl", + contract, + contract_deployer_skey, + ) + .with_standard_payment(MAX_GAS_FEE_PAYMENT_AMOUNT) // max amount allowed to be used on gas fees + .with_timestamp(Timestamp::now()) + .with_ttl(TimeDiff::from_millis(60_000)) // 1 min + .build()?; + + tracing::info!("Submitting contract deploy"); + let expected_rpc_id = JsonRpcId::Number(rand::thread_rng().gen::()); + let deploy_hash = put_deploy( + expected_rpc_id.clone(), + casper_node_rpc_url, + Verbosity::High, + deploy, + ) + .await + .map_err(Into::::into) + .and_then(|response| { + if response.id == expected_rpc_id { + Ok(response.result.deploy_hash) + } else { + Err(anyhow!("JSON RPC Id missmatch")) + } + })?; + + tracing::info!("Waiting for successful contract initialization"); + retry(ExponentialBackoff::default(), || async { + let expected_rpc_id = JsonRpcId::Number(rand::thread_rng().gen::()); + let response = get_deploy( + expected_rpc_id.clone(), + casper_node_rpc_url, + Verbosity::High, + deploy_hash, + false, + ) + .await + .map_err(|err| match &err { + Error::ResponseIsHttpError { .. } | Error::FailedToGetResponse { .. } => { + backoff::Error::transient(anyhow!(err)) + } + _ => backoff::Error::permanent(anyhow!(err)), + })?; + if response.id == expected_rpc_id { + match response.result.execution_results.first() { + Some(result) => match &result.result { + ExecutionResult::Failure { error_message, .. } => { + Err(backoff::Error::permanent(anyhow!(error_message.clone()))) + } + ExecutionResult::Success { .. } => Ok(()), + }, + Option::None => Err(backoff::Error::transient(anyhow!( + "No execution results there yet" + ))), + } + } else { + Err(backoff::Error::permanent(anyhow!("JSON RPC Id missmatch"))) + } + }) + .await?; + tracing::info!("Contract was deployed successfully"); + + tracing::info!("Fetching deployed contract hash"); + // Query global state + let expected_rpc_id = JsonRpcId::Number(rand::thread_rng().gen::()); + let state_root_hash = get_state_root_hash( + expected_rpc_id.clone(), + casper_node_rpc_url, + Verbosity::High, + Option::None, + ) + .await + .map_err(Into::::into) + .and_then(|response| { + if response.id == expected_rpc_id { + response + .result + .state_root_hash + .ok_or(anyhow!("No state root hash present in response")) + } else { + Err(anyhow!("JSON RPC Id missmatch")) + } + })?; + + let expected_rpc_id = JsonRpcId::Number(rand::thread_rng().gen::()); + let account = get_account( + expected_rpc_id.clone(), + casper_node_rpc_url, + Verbosity::High, + Option::None, + contract_deployer_pkey.clone(), + ) + .await + .map_err(Into::::into) + .and_then(|response| { + if response.id == expected_rpc_id { + Ok(response.result.account) + } else { + Err(anyhow!("JSON RPC Id missmatch")) + } + })?; + + let expected_rpc_id = JsonRpcId::Number(rand::thread_rng().gen::()); + let account_key = Key::Account(*account.account_hash()); + let contract_hash: casper_client_types::ContractHash = query_global_state( + expected_rpc_id.clone(), + casper_node_rpc_url, + Verbosity::High, + casper_client::rpcs::GlobalStateIdentifier::StateRootHash(state_root_hash), // fetches recent blocks state root hash + account_key, + vec![hash_name.clone()], + ) + .await + .map_err(Into::::into) + .and_then(|response| { + if response.id == expected_rpc_id { + match response.result.stored_value { + StoredValue::ContractPackage(contract_package) => Ok(*contract_package + .versions() + .next() + .expect("Expected at least one contract version") + .contract_hash()), + other => Err(anyhow!( + "Unexpected result type, type is not a CLValue: {:?}", + other + )), + } + } else { + Err(anyhow!("JSON RPC Id missmatch")) + } + })?; + tracing::info!( + "Successfully fetched the contract hash for {}: {}", + &hash_name, + &contract_hash + ); + Ok::<(String, casper_client_types::ContractHash), anyhow::Error>(( + hash_name.clone(), + contract_hash, + )) +} + #[cfg(test)] mod tests { use super::*; + use casper_client_types::runtime_args; + use hex::FromHex; + #[cfg_attr(not(feature = "cctl-tests"), ignore)] #[tokio::test] async fn test_cctl_network_starts_and_terminates() { - let network = CCTLNetwork::run(Option::None, Option::None, Option::None) - .await - .unwrap(); + let network = CCTLNetwork::run(None, None, None, None).await.unwrap(); for node in &network.nodes { if node.state == NodeState::Running { let node_status = get_node_status( @@ -177,4 +392,27 @@ mod tests { } } } + + #[cfg_attr(not(feature = "cctl-tests"), ignore)] + #[tokio::test] + async fn test_cctl_deploys_a_contract_successfully() { + let contract_wasm_path = + PathBuf::from(env!("PATH_TO_WASM_BINARIES")).join("demo-contract-optimized.wasm"); + let hash_name = "kairos_contract_package_hash"; + let contract_to_deploy = DeployableContract { + hash_name: hash_name.to_string(), + runtime_args: runtime_args! { "initial_trie_root" => Option::<[u8; 32]>::None }, + path: contract_wasm_path, + }; + let network = CCTLNetwork::run(None, Some(contract_to_deploy), None, None) + .await + .unwrap(); + let expected_contract_hash_path = network.working_dir.join("contracts").join(hash_name); + assert!(expected_contract_hash_path.exists()); + + let hash_string = fs::read_to_string(expected_contract_hash_path).unwrap(); + let contract_hash_bytes = <[u8; 32]>::from_hex(hash_string).unwrap(); + let contract_hash = ContractHash::new(contract_hash_bytes); + assert!(contract_hash.to_formatted_string().starts_with("contract-")) + } } diff --git a/nixos/modules/cctl.nix b/nixos/modules/cctl.nix index dbbe1653..e548dbe3 100644 --- a/nixos/modules/cctl.nix +++ b/nixos/modules/cctl.nix @@ -8,8 +8,6 @@ let mkEnableOption escapeShellArgs optionals - optional - concatLines ; cfg = config.services.cctl; in @@ -33,7 +31,7 @@ in }; workingDirectory = mkOption { - type = types.str; + type = types.path; default = "/var/lib/cctl"; description = '' The working directory path where cctl will put its assets and resources. @@ -63,18 +61,31 @@ in The log-level that should be used. ''; }; + + contract = mkOption { + type = types.nullOr (types.attrsOf types.path); + default = null; + example = { "contract hash name" = "/path/to/contract.wasm"; }; + description = '' + The wasm compiled contract that should be deployed once the network is up and ready. + The name of the attribute should correspond to the contracts hash name when calling + https://docs.rs/casper-contract/latest/casper_contract/contract_api/storage/fn.new_locked_contract.html + ''; + }; + }; config = mkIf cfg.enable { systemd.services.cctl = let - writeableChainspec = "${cfg.workingDirectory}/chainspec.toml"; - writeableConfig = "${cfg.workingDirectory}/config.toml"; args = escapeShellArgs ([ "--working-dir" cfg.workingDirectory ] + ++ optionals (!builtins.isNull cfg.contract) ([ + "--deploy-contract" + ] ++ (lib.mapAttrsToList (hash_name: contract_path: "${hash_name}:${contract_path}") cfg.contract)) ++ optionals (!builtins.isNull cfg.chainspec) [ "--chainspec-path" cfg.chainspec @@ -96,15 +107,12 @@ in serviceConfig = mkMerge [ { - ExecStartPre = - concatLines - ((optional (!builtins.isNull cfg.chainspec) "${pkgs.coreutils}/bin/cp --no-preserve=mode ${cfg.chainspec} ${writeableChainspec}") ++ - (optional (!builtins.isNull cfg.config) "${pkgs.coreutils}/bin/cp --no-preserve=mode ${cfg.config} ${writeableConfig}")); ExecStart = "${lib.getExe cfg.package} ${args}"; Type = "notify"; Restart = "no"; User = "cctl"; Group = "cctl"; + TimeoutStartSec = 1000; StateDirectory = builtins.baseNameOf cfg.workingDirectory; WorkingDirectory = cfg.workingDirectory; ReadWritePaths = [ @@ -129,12 +137,14 @@ in # when testing services.nginx = { enable = true; - virtualHosts."${config.networking.hostName}".locations."/cctl/users/" = { - alias = "${cfg.workingDirectory}/assets/users/"; - extraConfig = '' - autoindex on; - add_header Content-Type 'text/plain charset=UTF-8'; - ''; + virtualHosts."${config.networking.hostName}".locations = { + "/cctl/users/" = { + alias = "${cfg.workingDirectory}/assets/users/"; + extraConfig = '' + autoindex on; + add_header Content-Type 'text/plain charset=UTF-8'; + ''; + }; }; }; }; From f78dc5f512be2dc58981bd644916bf845b2f85bd Mon Sep 17 00:00:00 2001 From: Avi Dessauer Date: Fri, 21 Jun 2024 19:14:42 -0500 Subject: [PATCH 13/40] Update contract to use correct L1Deposit type --- Cargo.lock | 1 + kairos-contracts/Cargo.lock | 1 + .../contract-utils/src/constants.rs | 1 + .../demo-contract/contract-utils/src/lib.rs | 9 - .../demo-contract/contract/Cargo.toml | 2 +- .../contract/src/entry_points.rs | 3 +- .../demo-contract/contract/src/main.rs | 22 +- kairos-prover/Cargo.lock | 408 +++++++++++++++++- kairos-prover/kairos-circuit-logic/Cargo.toml | 3 + .../kairos-circuit-logic/src/transactions.rs | 4 + kairos-server/Cargo.toml | 2 +- kairos-server/src/l1_sync/event_manager.rs | 3 +- .../deposit-session/src/main.rs | 4 +- 13 files changed, 428 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c0476f3..709bd08b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2456,6 +2456,7 @@ name = "kairos-circuit-logic" version = "0.1.0" dependencies = [ "borsh", + "casper-event-standard", "kairos-trie", "kairos-tx", "proptest", diff --git a/kairos-contracts/Cargo.lock b/kairos-contracts/Cargo.lock index d147125d..706880ff 100644 --- a/kairos-contracts/Cargo.lock +++ b/kairos-contracts/Cargo.lock @@ -777,6 +777,7 @@ name = "kairos-circuit-logic" version = "0.1.0" dependencies = [ "borsh", + "casper-event-standard", "kairos-trie", "serde", "sha2", diff --git a/kairos-contracts/demo-contract/contract-utils/src/constants.rs b/kairos-contracts/demo-contract/contract-utils/src/constants.rs index 00fd9279..35b5c422 100644 --- a/kairos-contracts/demo-contract/contract-utils/src/constants.rs +++ b/kairos-contracts/demo-contract/contract-utils/src/constants.rs @@ -10,6 +10,7 @@ pub const RUNTIME_ARG_INITIAL_TRIE_ROOT: &str = "initial_trie_root"; pub const RUNTIME_ARG_TEMP_PURSE: &str = "temp_purse"; pub const RUNTIME_ARG_AMOUNT: &str = "amount"; pub const RUNTIME_ARG_RECEIPT: &str = "risc0_receipt"; +pub const RUNTIME_ARG_RECIPIENT: &str = "risc0_recipient"; pub const EP_INIT_NAME: &str = "init"; pub const EP_GET_PURSE_NAME: &str = "get_purse"; diff --git a/kairos-contracts/demo-contract/contract-utils/src/lib.rs b/kairos-contracts/demo-contract/contract-utils/src/lib.rs index 56bd8c05..4274e4ab 100644 --- a/kairos-contracts/demo-contract/contract-utils/src/lib.rs +++ b/kairos-contracts/demo-contract/contract-utils/src/lib.rs @@ -1,13 +1,4 @@ #![no_std] pub mod constants; -use casper_event_standard::casper_types::Key; -use casper_event_standard::Event; - extern crate alloc; - -#[derive(Event)] -pub struct Deposit { - pub depositor: Key, - pub amount: u64, -} diff --git a/kairos-contracts/demo-contract/contract/Cargo.toml b/kairos-contracts/demo-contract/contract/Cargo.toml index 0d951cc6..3f3863a6 100644 --- a/kairos-contracts/demo-contract/contract/Cargo.toml +++ b/kairos-contracts/demo-contract/contract/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true license.workspace = true [dependencies] -kairos-circuit-logic = {path="../../../kairos-prover/kairos-circuit-logic", default-features=false, features=["serde"]} +kairos-circuit-logic = {path="../../../kairos-prover/kairos-circuit-logic", default-features=false, features=["serde", "casper-event-standard"]} kairos-verifier-risc0-lib = {path="../../../kairos-prover/kairos-verifier-risc0-lib", default-features=false, features=["verifier", "disable-dev-mode"]} serde = {version="1", default-features=false, features=["derive"]} serde-json-wasm = { version="1", default-features=false } diff --git a/kairos-contracts/demo-contract/contract/src/entry_points.rs b/kairos-contracts/demo-contract/contract/src/entry_points.rs index 27db698c..3bb52b53 100644 --- a/kairos-contracts/demo-contract/contract/src/entry_points.rs +++ b/kairos-contracts/demo-contract/contract/src/entry_points.rs @@ -2,7 +2,7 @@ use alloc::vec; use casper_types::{CLType, EntryPoint, EntryPointAccess, EntryPointType, Parameter}; use contract_utils::constants::{ EP_DEPOSIT_NAME, EP_GET_PURSE_NAME, EP_INIT_NAME, EP_SUBMIT_NAME, RUNTIME_ARG_AMOUNT, - RUNTIME_ARG_RECEIPT, RUNTIME_ARG_TEMP_PURSE, + RUNTIME_ARG_RECEIPT, RUNTIME_ARG_RECIPIENT, RUNTIME_ARG_TEMP_PURSE, }; pub fn init() -> EntryPoint { @@ -29,6 +29,7 @@ pub fn deposit() -> EntryPoint { EntryPoint::new( EP_DEPOSIT_NAME, vec![ + Parameter::new(RUNTIME_ARG_RECIPIENT, CLType::PublicKey), Parameter::new(RUNTIME_ARG_AMOUNT, CLType::U512), Parameter::new(RUNTIME_ARG_TEMP_PURSE, CLType::URef), ], diff --git a/kairos-contracts/demo-contract/contract/src/main.rs b/kairos-contracts/demo-contract/contract/src/main.rs index a0435d5d..ca9fa55a 100644 --- a/kairos-contracts/demo-contract/contract/src/main.rs +++ b/kairos-contracts/demo-contract/contract/src/main.rs @@ -8,7 +8,7 @@ use casper_contract::{ unwrap_or_revert::UnwrapOrRevert, }; use casper_event_standard::Schemas; -use casper_types::bytesrepr::Bytes; +use casper_types::bytesrepr::{Bytes, ToBytes}; use casper_types::{ contracts::NamedKeys, runtime_args, AccessRights, ApiError, CLValue, EntryPoints, Key, RuntimeArgs, URef, U512, @@ -16,9 +16,9 @@ use casper_types::{ use contract_utils::constants::{ KAIROS_CONTRACT_HASH, KAIROS_CONTRACT_PACKAGE_HASH, KAIROS_CONTRACT_UREF, KAIROS_DEPOSIT_PURSE, KAIROS_LAST_PROCESSED_DEPOSIT_COUNTER, KAIROS_TRIE_ROOT, RUNTIME_ARG_AMOUNT, - RUNTIME_ARG_INITIAL_TRIE_ROOT, RUNTIME_ARG_RECEIPT, RUNTIME_ARG_TEMP_PURSE, + RUNTIME_ARG_INITIAL_TRIE_ROOT, RUNTIME_ARG_RECEIPT, RUNTIME_ARG_RECIPIENT, + RUNTIME_ARG_TEMP_PURSE, }; -use contract_utils::Deposit; mod entry_points; mod utils; use kairos_verifier_risc0_lib::verifier::Receipt; @@ -29,7 +29,7 @@ use utils::get_immediate_caller; #[allow(unused)] use casper_contract_no_std_helpers; -use kairos_circuit_logic::ProofOutputs; +use kairos_circuit_logic::{transactions::L1Deposit, ProofOutputs}; // This entry point is called once when the contract is installed. // The contract purse will be created in contract context so that it is "owned" by the contract @@ -41,7 +41,7 @@ pub extern "C" fn init() { } // initialize event schema - let schemas = Schemas::new().with::(); + let schemas = Schemas::new().with::(); casper_event_standard::init(schemas); let new_deposit_purse: URef = system::create_purse(); @@ -69,6 +69,7 @@ pub extern "C" fn get_purse() { #[no_mangle] pub extern "C" fn deposit() { let temp_purse: URef = runtime::get_named_arg(RUNTIME_ARG_TEMP_PURSE); + let recipient: casper_types::PublicKey = runtime::get_named_arg(RUNTIME_ARG_RECIPIENT); let amount: U512 = runtime::get_named_arg(RUNTIME_ARG_AMOUNT); let deposit_purse_uref: URef = runtime::get_key(KAIROS_DEPOSIT_PURSE) .unwrap_or_revert_with(DepositError::MissingKeyDepositPurse) @@ -81,10 +82,13 @@ pub extern "C" fn deposit() { let amount = u64::try_from(amount).unwrap_or_else(|_| runtime::revert(ApiError::InvalidArgument)); - let new_deposit_record: Deposit = Deposit { - depositor: get_immediate_caller().unwrap_or_revert(), - amount, - }; + // FIXME: verify that the caller's account hash matches a depositor public key argument. + // We have to ensure we know who the depositor is for regulatory reasons. + // We could check that the recipient of the funds is the caller or off chain get another signature from the public key. + let _account_hash = get_immediate_caller().unwrap_or_revert(); + + let recipient = recipient.into_bytes().unwrap_or_revert(); + let new_deposit_record: L1Deposit = L1Deposit { recipient, amount }; // this increases a counter automatically - we don't need to create one ourselves casper_event_standard::emit(new_deposit_record); } diff --git a/kairos-prover/Cargo.lock b/kairos-prover/Cargo.lock index 557db991..1a9e6d75 100644 --- a/kairos-prover/Cargo.lock +++ b/kairos-prover/Cargo.lock @@ -108,9 +108,9 @@ dependencies = [ "ark-serialize", "ark-snark", "ark-std", - "blake2", + "blake2 0.10.6", "derivative", - "digest", + "digest 0.10.7", "sha2", ] @@ -142,7 +142,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "digest", + "digest 0.10.7", "itertools 0.10.5", "num-bigint 0.4.5", "num-traits", @@ -222,7 +222,7 @@ checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive", "ark-std", - "digest", + "digest 0.10.7", "num-bigint 0.4.5", ] @@ -393,6 +393,24 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -405,6 +423,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bincode" version = "1.3.3" @@ -462,13 +486,24 @@ dependencies = [ "nom", ] +[[package]] +name = "blake2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" +dependencies = [ + "crypto-mac", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "blake2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -563,6 +598,64 @@ dependencies = [ "serde", ] +[[package]] +name = "casper-contract" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d42901eb5b09bb79e7d7403642e70983ccac0f4812edf1de77d978abea5f3299" +dependencies = [ + "casper-types", + "hex_fmt", +] + +[[package]] +name = "casper-event-standard" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3a1bdb142b4bfcdceec757422b2e292f446b72ce3613f881eb694f3925ef10" +dependencies = [ + "casper-contract", + "casper-event-standard-macro", + "casper-types", +] + +[[package]] +name = "casper-event-standard-macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "485810e6c8387863a92e9b81e4e66ce290e2c96c0ad8ec4352e95128aa88900e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "casper-types" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e01525b7bbae90fe9de3f1def6ffe05052a94ed7d14b1c2b38baec81eeec31b" +dependencies = [ + "base16", + "base64 0.13.1", + "bitflags 1.3.2", + "blake2 0.9.2", + "ed25519-dalek", + "hex", + "hex_fmt", + "k256", + "num", + "num-derive", + "num-integer", + "num-rational", + "num-traits", + "rand", + "serde", + "serde_bytes", + "serde_json", + "uint", +] + [[package]] name = "cc" version = "1.0.99" @@ -717,13 +810,22 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-bigint" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ + "generic-array", + "rand_core", "subtle", + "zeroize", ] [[package]] @@ -736,6 +838,43 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.67", +] + [[package]] name = "cust" version = "0.3.2" @@ -782,6 +921,16 @@ dependencies = [ "find_cuda_helper", ] +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "derivative" version = "2.2.0" @@ -826,6 +975,15 @@ dependencies = [ "syn 2.0.67", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" @@ -894,7 +1052,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05213e96f184578b5f70105d4d0a644a168e99e12d7bea0b200c15d67b5c182" dependencies = [ - "digest", + "digest 0.10.7", "futures", "rand", "reqwest 0.11.27", @@ -902,6 +1060,43 @@ dependencies = [ "tokio", ] +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.12.0" @@ -914,6 +1109,24 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.34" @@ -990,6 +1203,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find_cuda_helper" version = "0.2.0" @@ -1154,6 +1373,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1192,6 +1412,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" version = "0.3.26" @@ -1274,6 +1505,21 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hex_fmt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "home" version = "0.5.9" @@ -1521,11 +1767,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + [[package]] name = "kairos-circuit-logic" version = "0.1.0" dependencies = [ "borsh", + "casper-event-standard", "kairos-trie", "kairos-tx", "proptest", @@ -1561,7 +1820,7 @@ version = "0.1.0" source = "git+https://github.com/cspr-rad/kairos-trie#35367cb60a0ac1cba86e5a6ac3e87b04ae18255f" dependencies = [ "bumpalo", - "digest", + "digest 0.10.7", "ouroboros", "serde", ] @@ -1827,6 +2086,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint 0.4.5", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.3.3" @@ -1857,6 +2130,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -1866,6 +2150,28 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint 0.4.5", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1930,6 +2236,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "option-ext" version = "0.2.0" @@ -2034,6 +2346,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2467,6 +2789,16 @@ dependencies = [ "winreg 0.52.0", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.8" @@ -2644,11 +2976,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53342780aef2d31ccc0526e6d4b151dab69e678d2e1495b2270ed40f5e1df6f4" dependencies = [ "anyhow", - "blake2", + "blake2 0.10.6", "bytemuck", "cfg-if", "cust", - "digest", + "digest 0.10.7", "ff", "hex", "hex-literal", @@ -2884,6 +3216,19 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.23" @@ -2899,6 +3244,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.203" @@ -2964,7 +3318,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -2974,7 +3328,7 @@ source = "git+https://github.com/risc0/RustCrypto-hashes?tag=sha2-v0.10.6-riscze dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -2986,6 +3340,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -3045,6 +3409,16 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sppark" version = "0.1.6" @@ -3484,6 +3858,18 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unarray" version = "0.1.4" diff --git a/kairos-prover/kairos-circuit-logic/Cargo.toml b/kairos-prover/kairos-circuit-logic/Cargo.toml index 11c8a25c..320292ce 100644 --- a/kairos-prover/kairos-circuit-logic/Cargo.toml +++ b/kairos-prover/kairos-circuit-logic/Cargo.toml @@ -12,6 +12,7 @@ asn1 = ["dep:kairos-tx"] borsh = ["dep:borsh"] arbitrary = ["dep:proptest", "dep:test-strategy", "std", "test-logic"] test-logic = [] +casper-event-standard = ["dep:casper-event-standard"] [dependencies] kairos-tx = { path = "../../kairos-tx", default-features = false, optional = true } @@ -20,6 +21,8 @@ serde = { version = "1", default-features = false, features = ["derive", "alloc" sha2 = { version = "0.10", default-features = false } borsh = { version = "1", default-features = false, features = ["derive"], optional = true } +casper-event-standard = { version = "0.5", optional = true } + # dev dependencies enabled by arbitrary test-strategy = { version = "0.3", optional = true } proptest = { version = "1", optional = true } diff --git a/kairos-prover/kairos-circuit-logic/src/transactions.rs b/kairos-prover/kairos-circuit-logic/src/transactions.rs index 43e7c527..1ef4be8d 100644 --- a/kairos-prover/kairos-circuit-logic/src/transactions.rs +++ b/kairos-prover/kairos-circuit-logic/src/transactions.rs @@ -51,6 +51,10 @@ pub struct Transfer { feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize) )] +#[cfg_attr( + feature = "casper-event-standard", + derive(casper_event_standard::Event) +)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct L1Deposit { pub recipient: PublicKey, diff --git a/kairos-server/Cargo.toml b/kairos-server/Cargo.toml index f12a8cc4..560cd066 100644 --- a/kairos-server/Cargo.toml +++ b/kairos-server/Cargo.toml @@ -36,7 +36,7 @@ tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] } hex = "0.4" kairos-tx = { path = "../kairos-tx" } contract-utils = { path = "../kairos-contracts/demo-contract/contract-utils" } -kairos-circuit-logic = { path = "../kairos-prover/kairos-circuit-logic", features = ["serde", "asn1"] } +kairos-circuit-logic = { path = "../kairos-prover/kairos-circuit-logic", features = ["serde", "asn1", "casper-event-standard"] } kairos-trie = { git = "https://github.com/cspr-rad/kairos-trie" } sha2 = "0.10" reqwest = { version = "0.12", features = ["json"] } diff --git a/kairos-server/src/l1_sync/event_manager.rs b/kairos-server/src/l1_sync/event_manager.rs index 17a3b27f..1f71a16c 100644 --- a/kairos-server/src/l1_sync/event_manager.rs +++ b/kairos-server/src/l1_sync/event_manager.rs @@ -4,7 +4,6 @@ use casper_event_toolkit::casper_types::bytesrepr::FromBytes; use casper_event_toolkit::fetcher::{Fetcher, Schemas}; use casper_event_toolkit::metadata::CesMetadataRef; use casper_event_toolkit::rpc::client::CasperClient; -use contract_utils::Deposit; use crate::state::ServerStateInner; use kairos_circuit_logic::transactions::{KairosTransaction, L1Deposit}; @@ -60,7 +59,7 @@ impl EventManager { match event.name.as_str() { "Deposit" => { // Parse simplified deposit data. - let (deposit, _) = Deposit::from_bytes(&event_bytes) + let (deposit, _) = L1Deposit::from_bytes(&event_bytes) .expect("Failed to parse deposit event from bytes"); let amount = deposit.amount; diff --git a/kairos-session-code/deposit-session/src/main.rs b/kairos-session-code/deposit-session/src/main.rs index 107eba84..d707a8a4 100644 --- a/kairos-session-code/deposit-session/src/main.rs +++ b/kairos-session-code/deposit-session/src/main.rs @@ -9,11 +9,12 @@ #![no_main] use casper_contract::contract_api::{account, runtime, system}; -use casper_types::{runtime_args, ContractHash, RuntimeArgs, URef, U512}; +use casper_types::{runtime_args, ContractHash, PublicKey, RuntimeArgs, URef, U512}; #[no_mangle] pub extern "C" fn call() { let contract_hash: ContractHash = runtime::get_named_arg("demo_contract"); + let public_key: PublicKey = runtime::get_named_arg("recipient"); let amount: U512 = runtime::get_named_arg("amount"); let source: URef = account::get_main_purse(); // create a temporary purse that can be passed to the contract @@ -30,6 +31,7 @@ pub extern "C" fn call() { "deposit", runtime_args! { "temp_purse" => temp_purse, + "recipient" => public_key, "amount" => amount }, ); From afafb9f14326b7cc13e6aa32147ad5bb08ce9891 Mon Sep 17 00:00:00 2001 From: Avi Dessauer Date: Mon, 24 Jun 2024 23:58:40 -0500 Subject: [PATCH 14/40] Implement cli transfer and withdraw calls --- Cargo.lock | 1 + kairos-cli/Cargo.toml | 1 + kairos-cli/src/commands/transfer.rs | 31 ++++++++++++++++++++++++----- kairos-cli/src/commands/withdraw.rs | 31 ++++++++++++++++++++++++----- kairos-cli/src/common/args.rs | 6 ++++++ kairos-cli/src/lib.rs | 4 ++-- 6 files changed, 62 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 709bd08b..e7958c70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2470,6 +2470,7 @@ name = "kairos-cli" version = "0.1.0" dependencies = [ "assert_cmd", + "axum-extra", "casper-client", "casper-hashing 2.0.0", "casper-types 3.0.0", diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index ad4c80f5..09d95b2e 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -26,6 +26,7 @@ thiserror = "1" kairos-crypto = { path = "../kairos-crypto", features = ["std"] } kairos-tx = { path = "../kairos-tx" } kairos-server = { path = "../kairos-server" } +axum-extra = { version = "0.9", features = [ "typed-routing" ] } reqwest = { version = "0.12", features = ["blocking", "json"] } serde_json = "1.0" serde = "1.0" diff --git a/kairos-cli/src/commands/transfer.rs b/kairos-cli/src/commands/transfer.rs index b694bc77..51ba4699 100644 --- a/kairos-cli/src/commands/transfer.rs +++ b/kairos-cli/src/commands/transfer.rs @@ -1,13 +1,19 @@ -use crate::common::args::{AmountArg, PrivateKeyPathArg}; +use crate::client::KairosClientError; +use crate::common::args::{AmountArg, NonceArg, PrivateKeyPathArg}; use crate::error::CliError; use crate::utils::parse_hex_string; +use axum_extra::routing::TypedPath; use kairos_crypto::error::CryptoError; use kairos_crypto::implementations::Signer; use kairos_crypto::SignerCore; use kairos_crypto::SignerFsExtension; use clap::Parser; +use kairos_server::routes::transfer::TransferPath; +use kairos_server::routes::PayloadBody; +use kairos_tx::asn::{SigningPayload, Transfer}; +use reqwest::Url; #[derive(Parser)] pub struct Args { @@ -17,17 +23,32 @@ pub struct Args { amount: AmountArg, #[clap(flatten)] private_key_path: PrivateKeyPathArg, + #[clap(flatten)] + nonce: NonceArg, } -pub fn run(args: Args) -> Result { - let _recipient = Signer::from_public_key(args.recipient)?.to_public_key()?; - let _amount: u64 = args.amount.field; - let _signer = +pub async fn run(args: Args, kairos_server_address: Url) -> Result { + let recipient = Signer::from_public_key(args.recipient)?.to_public_key()?; + 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; // TODO: Create transaction and sign it with `signer`. // TODO: Send transaction to the network, using Rust SDK. + reqwest::Client::new() + .post(kairos_server_address.join(TransferPath::PATH).unwrap()) + .json(&PayloadBody { + public_key: signer.to_public_key()?, + payload: SigningPayload::new(nonce, Transfer::new(recipient, amount)) + .try_into() + .unwrap(), + signature: vec![], + }) + .send() + .await + .map_err(KairosClientError::from)?; Ok("ok".to_string()) } diff --git a/kairos-cli/src/commands/withdraw.rs b/kairos-cli/src/commands/withdraw.rs index 8e710a76..2defff04 100644 --- a/kairos-cli/src/commands/withdraw.rs +++ b/kairos-cli/src/commands/withdraw.rs @@ -1,11 +1,17 @@ -use crate::common::args::{AmountArg, PrivateKeyPathArg}; +use crate::client::KairosClientError; +use crate::common::args::{AmountArg, NonceArg, PrivateKeyPathArg}; use crate::error::CliError; use kairos_crypto::error::CryptoError; use kairos_crypto::implementations::Signer; -use kairos_crypto::SignerFsExtension; +use kairos_crypto::{SignerCore, SignerFsExtension}; use clap::Parser; +use kairos_server::routes::transfer::TransferPath; +use kairos_server::routes::withdraw::WithdrawPath; +use kairos_server::routes::PayloadBody; +use kairos_tx::asn::{SigningPayload, Withdrawal}; +use reqwest::Url; #[derive(Parser)] pub struct Args { @@ -13,16 +19,31 @@ pub struct Args { amount: AmountArg, #[clap(flatten)] private_key_path: PrivateKeyPathArg, + #[clap(flatten)] + nonce: NonceArg, } -pub fn run(args: Args) -> Result { - let _amount: u64 = args.amount.field; - let _signer = +pub async 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; // TODO: Create transaction and sign it with `signer`. // TODO: Send transaction to the network, using Rust SDK. + reqwest::Client::new() + .post(kairos_server_address.join(WithdrawPath::PATH).unwrap()) + .json(&PayloadBody { + public_key: signer.to_public_key()?, + payload: SigningPayload::new(nonce, Withdrawal::new(amount)) + .try_into() + .unwrap(), + signature: vec![], + }) + .send() + .await + .map_err(KairosClientError::from)?; Ok("ok".to_string()) } diff --git a/kairos-cli/src/common/args.rs b/kairos-cli/src/common/args.rs index c2a4364a..d74ab3eb 100644 --- a/kairos-cli/src/common/args.rs +++ b/kairos-cli/src/common/args.rs @@ -13,3 +13,9 @@ pub struct PrivateKeyPathArg { #[arg(id = "private-key", long, short = 'k', value_name = "FILE_PATH")] pub field: PathBuf, } + +#[derive(Args, Debug)] +pub struct NonceArg { + #[arg(id = "nonce", long, short, value_name = "NUM")] + pub val: u64, +} diff --git a/kairos-cli/src/lib.rs b/kairos-cli/src/lib.rs index 4a5c52d8..2d56b0a0 100644 --- a/kairos-cli/src/lib.rs +++ b/kairos-cli/src/lib.rs @@ -36,7 +36,7 @@ pub fn run( ) -> Result { match command { Command::Deposit(args) => commands::deposit::run(args, kairos_server_address), - Command::Transfer(args) => commands::transfer::run(args), - Command::Withdraw(args) => commands::withdraw::run(args), + Command::Transfer(args) => commands::transfer::run(args, kairos_server_address), + Command::Withdraw(args) => commands::withdraw::run(args, kairos_server_address), } } From faa7a2ddf62cfb5d4d22713ea7d77f40e1c56b6c Mon Sep 17 00:00:00 2001 From: Avi Dessauer Date: Tue, 25 Jun 2024 09:52:28 -0500 Subject: [PATCH 15/40] Fixup --- kairos-cli/src/client.rs | 1 + kairos-cli/src/commands/transfer.rs | 8 +++----- kairos-cli/src/commands/withdraw.rs | 7 +++---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/kairos-cli/src/client.rs b/kairos-cli/src/client.rs index 3d10584b..c059d07e 100644 --- a/kairos-cli/src/client.rs +++ b/kairos-cli/src/client.rs @@ -45,6 +45,7 @@ pub fn deposit( depositor_secret_key: &SecretKey, amount: u64, ) -> Result { + // TODO let deposit_session_wasm_path = Path::new(env!("PATH_TO_SESSION_BINARIES")).join("deposit-session-optimized.wasm"); let deposit_session_wasm_bytes = fs::read(&deposit_session_wasm_path).unwrap_or_else(|err| { diff --git a/kairos-cli/src/commands/transfer.rs b/kairos-cli/src/commands/transfer.rs index 51ba4699..838cbbfd 100644 --- a/kairos-cli/src/commands/transfer.rs +++ b/kairos-cli/src/commands/transfer.rs @@ -10,8 +10,7 @@ use kairos_crypto::SignerCore; use kairos_crypto::SignerFsExtension; use clap::Parser; -use kairos_server::routes::transfer::TransferPath; -use kairos_server::routes::PayloadBody; +use kairos_server::routes::{transfer::TransferPath, PayloadBody}; use kairos_tx::asn::{SigningPayload, Transfer}; use reqwest::Url; @@ -27,7 +26,7 @@ pub struct Args { nonce: NonceArg, } -pub async fn run(args: Args, kairos_server_address: Url) -> Result { +pub fn run(args: Args, kairos_server_address: Url) -> Result { let recipient = Signer::from_public_key(args.recipient)?.to_public_key()?; let amount: u64 = args.amount.field; let signer = @@ -37,7 +36,7 @@ pub async fn run(args: Args, kairos_server_address: Url) -> Result Result Result { +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)?; @@ -32,7 +32,7 @@ pub async fn run(args: Args, kairos_server_address: Url) -> Result Result Date: Tue, 25 Jun 2024 10:03:39 -0500 Subject: [PATCH 16/40] Fix cli tests --- kairos-cli/tests/cli_tests.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index 5e3b6f7b..a05b49bf 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -66,9 +66,15 @@ fn transfer_successful_with_secp256k1() { .arg(recipient) .arg("--amount") .arg("123") + .arg("--nonce") + .arg("0") .arg("--private-key") .arg(secret_key_path); - cmd.assert().success().stdout("ok\n"); + + // the transfer command should fail because the server is not running + cmd.assert() + .failure() + .stderr(predicates::str::contains("http client error")); } #[test] @@ -79,9 +85,15 @@ fn withdraw_successful_with_ed25519() { cmd.arg("withdraw") .arg("--amount") .arg("123") + .arg("--nonce") + .arg("0") .arg("--private-key") .arg(secret_key_path); - cmd.assert().success().stdout("ok\n"); + + // the transfer command should fail because the server is not running + cmd.assert() + .failure() + .stderr(predicates::str::contains("http client error")); } #[test] From 4350c3193ac5a3a8c32b4d947d62bc8ba36b0e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Fri, 24 May 2024 11:49:52 +0200 Subject: [PATCH 17/40] kairos: normalize and use latest casper-types version --- kairos-cli/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index 09d95b2e..4477f42a 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -19,8 +19,8 @@ cctl-tests = [] [dependencies] casper-client.workspace = true -casper-client-types = { workspace = true, features = ["std"] } # TODO: Change `std` -> `std-fs-io` in the future version. -clap = { version = "4.5", features = ["derive", "deprecated"] } +casper-types = { version = "4", features = ["std-fs-io"] } +clap = { version = "4", features = ["derive", "deprecated"] } hex = "0.4" thiserror = "1" kairos-crypto = { path = "../kairos-crypto", features = ["std"] } From df5ec13139011e684a494d357bbd998bbec10c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Fri, 31 May 2024 12:21:47 +0200 Subject: [PATCH 18/40] kairos-cli: fixup casper-client invocations after update --- kairos-cli/src/client.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/kairos-cli/src/client.rs b/kairos-cli/src/client.rs index c059d07e..16f237a0 100644 --- a/kairos-cli/src/client.rs +++ b/kairos-cli/src/client.rs @@ -56,16 +56,13 @@ pub fn deposit( }); let deposit_session = ExecutableDeployItem::new_module_bytes(deposit_session_wasm_bytes.into(), runtime_args! {}); - let deploy = DeployBuilder::new( - env!("CASPER_CHAIN_NAME"), - deposit_session, - depositor_secret_key, - ) - .with_standard_payment(amount) - .with_timestamp(Timestamp::now()) - .with_ttl(TimeDiff::from_millis(60_000)) // 1 min - .build() - .map_err(|err| KairosClientError::CasperClientError(err.to_string()))?; + let deploy = DeployBuilder::new(env!("CASPER_CHAIN_NAME"), deposit_session) + .with_secret_key(depositor_secret_key) + .with_standard_payment(amount) + .with_timestamp(Timestamp::now()) + .with_ttl(TimeDiff::from_millis(60_000)) // 1 min + .build() + .map_err(|err| KairosClientError::CasperClientError(err.to_string()))?; let client = blocking::Client::new(); let url = base_url.join("/api/v1/deposit").unwrap(); From 9c337f04fef49f5f76a41611d974352ac6916ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Fri, 31 May 2024 13:43:17 +0200 Subject: [PATCH 19/40] update casper-types and casper-hashing --- kairos-cli/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index 4477f42a..350a1978 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -37,3 +37,4 @@ assert_cmd = "2" predicates = "3" kairos-test-utils = { path = "../kairos-test-utils" } casper-client-hashing.workspace = true +casper-hashing = "3" From 8ab3c9fd284ae40fb0fe1f476f556636ea8522fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Tue, 4 Jun 2024 15:10:39 +0200 Subject: [PATCH 20/40] kairos-server: add casper sse URL configuration option --- kairos-cli/tests/cli_tests.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index a05b49bf..d4bddc3b 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -23,9 +23,15 @@ async fn deposit_successful_with_ed25519() { .nodes .first() .expect("Expected at least one node after successful network run"); - let node_url = Url::parse(&format!("http://localhost:{}/rpc", node.port.rpc_port)).unwrap(); + let casper_rpc_url = + Url::parse(&format!("http://localhost:{}/rpc", node.port.rpc_port)).unwrap(); + let casper_sse_url = Url::parse(&format!( + "http://localhost:{}/events/main", + node.port.sse_port + )) + .unwrap(); - let kairos = kairos::Kairos::run(node_url, None).await.unwrap(); + let kairos = kairos::Kairos::run(casper_rpc_url, None).await.unwrap(); tokio::task::spawn_blocking(move || { let depositor_secret_key_path = network From d4a7ded647491e583c4967d8382828fca0c15c5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Wed, 5 Jun 2024 11:46:56 +0200 Subject: [PATCH 21/40] kairos/kairos-cli: add contract hash argument, pass it as a Key --- kairos-cli/src/client.rs | 25 +++++++++++++++----- kairos-cli/src/commands/deposit.rs | 38 ++++++++++++++++++------------ kairos-cli/src/common/args.rs | 5 ++++ 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/kairos-cli/src/client.rs b/kairos-cli/src/client.rs index 16f237a0..15bc3e04 100644 --- a/kairos-cli/src/client.rs +++ b/kairos-cli/src/client.rs @@ -1,12 +1,15 @@ use casper_client::types::DeployHash; use casper_client::types::{DeployBuilder, ExecutableDeployItem, TimeDiff, Timestamp}; -use casper_client_types::{crypto::SecretKey, runtime_args, RuntimeArgs}; +use casper_types::{crypto::SecretKey, runtime_args, Key, RuntimeArgs, U512}; use reqwest::{blocking, Url}; use serde::{Deserialize, Serialize}; use std::fmt; use std::fs; use std::path::Path; +// max amount allowed to be used on gas fees +pub const MAX_GAS_FEE_PAYMENT_AMOUNT: u64 = 1_000_000_000_000; + #[derive(PartialOrd, Ord, PartialEq, Eq, Debug, Serialize, Deserialize)] pub enum KairosClientError { ResponseError(String), @@ -40,10 +43,11 @@ impl From for KairosClientError { } } -pub fn deposit( +pub fn deposit>( base_url: &Url, depositor_secret_key: &SecretKey, - amount: u64, + contract_hash: &str, + amount: A, ) -> Result { // TODO let deposit_session_wasm_path = @@ -54,11 +58,20 @@ pub fn deposit( deposit_session_wasm_path, err ) }); - let deposit_session = - ExecutableDeployItem::new_module_bytes(deposit_session_wasm_bytes.into(), runtime_args! {}); + let contract_hash = Key::from_formatted_str(contract_hash).map_err(|err| { + panic!( + "Failed to parse the contract hash {}: {}", + contract_hash, err + ) + }); + let deposit_session = ExecutableDeployItem::new_module_bytes( + deposit_session_wasm_bytes.into(), + runtime_args! { "demo_contract" => contract_hash, "amount" => amount.into() }, + ); let deploy = DeployBuilder::new(env!("CASPER_CHAIN_NAME"), deposit_session) .with_secret_key(depositor_secret_key) - .with_standard_payment(amount) + // max amount allowed to be used on gas fees + .with_standard_payment(MAX_GAS_FEE_PAYMENT_AMOUNT) .with_timestamp(Timestamp::now()) .with_ttl(TimeDiff::from_millis(60_000)) // 1 min .build() diff --git a/kairos-cli/src/commands/deposit.rs b/kairos-cli/src/commands/deposit.rs index 4fe4e363..a7f4f201 100644 --- a/kairos-cli/src/commands/deposit.rs +++ b/kairos-cli/src/commands/deposit.rs @@ -1,5 +1,5 @@ use crate::client; -use crate::common::args::{AmountArg, PrivateKeyPathArg}; +use crate::common::args::{AmountArg, ContractHashArg, PrivateKeyPathArg}; use crate::error::CliError; use casper_client_types::crypto::SecretKey; @@ -12,27 +12,35 @@ pub struct Args { amount: AmountArg, #[clap(flatten)] private_key_path: PrivateKeyPathArg, + #[clap(flatten)] + contract_hash: ContractHashArg, } pub fn run(args: Args, kairos_server_address: Url) -> Result { + let contract_hash = args.contract_hash.field; let amount: u64 = args.amount.field; let path = args.private_key_path.field; let depositor_secret_key = SecretKey::from_file(&path) .map_err(|err| panic!("Failed to read secret key from file {:?}: {}", path, err)) .unwrap(); - client::deposit(&kairos_server_address, &depositor_secret_key, amount) - .map_err(Into::::into) - .map(|deploy_hash| { - // to_string crops the hash to .. - // thus we use serde to get the full string, and remove the - // double quotes that get added during serialization - let mut output: String = serde_json::to_string(&deploy_hash) - .unwrap() - .chars() - .filter(|&c| c != '"') // Filter out the double quotes - .collect(); - output.push('\n'); - output - }) + client::deposit( + &kairos_server_address, + &depositor_secret_key, + &contract_hash, + amount, + ) + .map_err(Into::::into) + .map(|deploy_hash| { + // to_string crops the hash to .. + // thus we use serde to get the full string, and remove the + // double quotes that get added during serialization + let mut output: String = serde_json::to_string(&deploy_hash) + .unwrap() + .chars() + .filter(|&c| c != '"') // Filter out the double quotes + .collect(); + output.push('\n'); + output + }) } diff --git a/kairos-cli/src/common/args.rs b/kairos-cli/src/common/args.rs index d74ab3eb..9def0d6a 100644 --- a/kairos-cli/src/common/args.rs +++ b/kairos-cli/src/common/args.rs @@ -19,3 +19,8 @@ pub struct NonceArg { #[arg(id = "nonce", long, short, value_name = "NUM")] pub val: u64, } + +pub struct ContractHashArg { + #[arg(id = "contract-hash", long, short = 'h', value_name = "CONTRACT_HASH")] + pub field: String, +} From b973ed4069754ee87119b4363bd27a491bef783b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Wed, 5 Jun 2024 16:27:03 +0200 Subject: [PATCH 22/40] kairos-cli: pass a ContractHash to the demo-contract --- kairos-cli/src/client.rs | 30 +++++++++++++----------------- kairos-cli/src/commands/deposit.rs | 15 +++++++++++---- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/kairos-cli/src/client.rs b/kairos-cli/src/client.rs index 15bc3e04..ea665ee1 100644 --- a/kairos-cli/src/client.rs +++ b/kairos-cli/src/client.rs @@ -1,6 +1,6 @@ use casper_client::types::DeployHash; use casper_client::types::{DeployBuilder, ExecutableDeployItem, TimeDiff, Timestamp}; -use casper_types::{crypto::SecretKey, runtime_args, Key, RuntimeArgs, U512}; +use casper_client_types::{crypto::SecretKey, runtime_args, ContractHash, RuntimeArgs, U512}; use reqwest::{blocking, Url}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -46,7 +46,7 @@ impl From for KairosClientError { pub fn deposit>( base_url: &Url, depositor_secret_key: &SecretKey, - contract_hash: &str, + contract_hash: &ContractHash, amount: A, ) -> Result { // TODO @@ -58,24 +58,20 @@ pub fn deposit>( deposit_session_wasm_path, err ) }); - let contract_hash = Key::from_formatted_str(contract_hash).map_err(|err| { - panic!( - "Failed to parse the contract hash {}: {}", - contract_hash, err - ) - }); let deposit_session = ExecutableDeployItem::new_module_bytes( deposit_session_wasm_bytes.into(), - runtime_args! { "demo_contract" => contract_hash, "amount" => amount.into() }, + runtime_args! { "demo_contract" => *contract_hash, "amount" => amount.into() }, ); - let deploy = DeployBuilder::new(env!("CASPER_CHAIN_NAME"), deposit_session) - .with_secret_key(depositor_secret_key) - // max amount allowed to be used on gas fees - .with_standard_payment(MAX_GAS_FEE_PAYMENT_AMOUNT) - .with_timestamp(Timestamp::now()) - .with_ttl(TimeDiff::from_millis(60_000)) // 1 min - .build() - .map_err(|err| KairosClientError::CasperClientError(err.to_string()))?; + let deploy = DeployBuilder::new( + env!("CASPER_CHAIN_NAME"), + deposit_session, + &depositor_secret_key, + ) + .with_standard_payment(MAX_GAS_FEE_PAYMENT_AMOUNT) // max amount allowed to be used on gas fees + .with_timestamp(Timestamp::now()) + .with_ttl(TimeDiff::from_millis(60_000)) // 1 min + .build() + .map_err(|err| KairosClientError::CasperClientError(err.to_string()))?; let client = blocking::Client::new(); let url = base_url.join("/api/v1/deposit").unwrap(); diff --git a/kairos-cli/src/commands/deposit.rs b/kairos-cli/src/commands/deposit.rs index a7f4f201..bf690cfb 100644 --- a/kairos-cli/src/commands/deposit.rs +++ b/kairos-cli/src/commands/deposit.rs @@ -2,10 +2,13 @@ use crate::client; use crate::common::args::{AmountArg, ContractHashArg, PrivateKeyPathArg}; use crate::error::CliError; -use casper_client_types::crypto::SecretKey; +use casper_client_types::{crypto::SecretKey, ContractHash}; use clap::Parser; +use hex::FromHex; use reqwest::Url; +use kairos_crypto::error::CryptoError; + #[derive(Parser, Debug)] pub struct Args { #[clap(flatten)] @@ -20,9 +23,13 @@ pub fn run(args: Args, kairos_server_address: Url) -> Result { let contract_hash = args.contract_hash.field; let amount: u64 = args.amount.field; let path = args.private_key_path.field; - let depositor_secret_key = SecretKey::from_file(&path) - .map_err(|err| panic!("Failed to read secret key from file {:?}: {}", path, err)) - .unwrap(); + let depositor_secret_key = + SecretKey::from_file(&path).map_err(|err| CryptoError::FailedToParseKey { + error: err.to_string(), + })?; + + let contract_hash_bytes = <[u8; 32]>::from_hex(contract_hash)?; + let contract_hash = ContractHash::new(contract_hash_bytes); client::deposit( &kairos_server_address, From 987f6b06d68cec3e345895d818c3f383dde46e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Thu, 6 Jun 2024 09:29:53 +0200 Subject: [PATCH 23/40] kairos: use workspace dependencies --- kairos-cli/Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index 350a1978..3643385f 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -23,13 +23,14 @@ casper-types = { version = "4", features = ["std-fs-io"] } clap = { version = "4", features = ["derive", "deprecated"] } hex = "0.4" thiserror = "1" -kairos-crypto = { path = "../kairos-crypto", features = ["std"] } +kairos-crypto = { path = "../kairos-crypto", features = [ "std", "fs" ] } kairos-tx = { path = "../kairos-tx" } kairos-server = { path = "../kairos-server" } axum-extra = { version = "0.9", features = [ "typed-routing" ] } reqwest = { version = "0.12", features = ["blocking", "json"] } serde_json = "1.0" serde = "1.0" +thiserror = "1" [dev-dependencies] tokio = "1" @@ -37,4 +38,4 @@ assert_cmd = "2" predicates = "3" kairos-test-utils = { path = "../kairos-test-utils" } casper-client-hashing.workspace = true -casper-hashing = "3" +casper-hashing.workspace = true From e0b7013dacc5b1a63aad1e3ed1491a191737e232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Thu, 6 Jun 2024 16:03:55 +0200 Subject: [PATCH 24/40] kairos: fixup tests --- kairos-cli/Cargo.toml | 1 + kairos-cli/src/common/args.rs | 2 +- kairos-cli/tests/cli_tests.rs | 14 +++++++++++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index 3643385f..369c6958 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -39,3 +39,4 @@ predicates = "3" kairos-test-utils = { path = "../kairos-test-utils" } casper-client-hashing.workspace = true casper-hashing.workspace = true +casper-client-hashing.workspace = true diff --git a/kairos-cli/src/common/args.rs b/kairos-cli/src/common/args.rs index 9def0d6a..d9ff7501 100644 --- a/kairos-cli/src/common/args.rs +++ b/kairos-cli/src/common/args.rs @@ -21,6 +21,6 @@ pub struct NonceArg { } pub struct ContractHashArg { - #[arg(id = "contract-hash", long, short = 'h', value_name = "CONTRACT_HASH")] + #[arg(id = "contract-hash", long, short = 'c', value_name = "CONTRACT_HASH")] pub field: String, } diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index d4bddc3b..bc73070b 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -42,6 +42,8 @@ async fn deposit_successful_with_ed25519() { cmd.arg("--kairos-server-address") .arg(kairos.url.as_str()) .arg("deposit") + .arg("--contract-hash") + .arg("000000000000000000000000000000000000") .arg("--amount") .arg("123") .arg("--private-key") @@ -108,6 +110,8 @@ fn deposit_invalid_amount() { let mut cmd = Command::cargo_bin("kairos-cli").unwrap(); cmd.arg("deposit") + .arg("--contract-hash") + .arg("000000000000000000000000000000000000") .arg("--amount") .arg("foo") // Invalid amount .arg("--private-key") @@ -123,6 +127,8 @@ fn deposit_invalid_private_key_path() { let mut cmd = Command::cargo_bin("kairos-cli").unwrap(); cmd.arg("deposit") + .arg("--contract-hash") + .arg("000000000000000000000000000000000000") .arg("--amount") .arg("123") .arg("--private-key") @@ -138,13 +144,15 @@ fn deposit_invalid_private_key_content() { let mut cmd = Command::cargo_bin("kairos-cli").unwrap(); cmd.arg("deposit") + .arg("--contract-hash") + .arg("000000000000000000000000000000000000") .arg("--amount") .arg("123") .arg("--private-key") .arg(secret_key_path); - cmd.assert().failure().stderr(predicates::str::contains( - "Failed to read secret key from file", - )); + cmd.assert() + .failure() + .stderr(predicates::str::contains("cryptography error")); } #[test] From d8a3b4e3238cebae6702c87f49dc3763fb597d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Fri, 7 Jun 2024 14:04:05 +0200 Subject: [PATCH 25/40] kairos-cli: fix linter complaints --- kairos-cli/src/client.rs | 2 +- kairos-cli/src/commands/deposit.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kairos-cli/src/client.rs b/kairos-cli/src/client.rs index ea665ee1..11529d91 100644 --- a/kairos-cli/src/client.rs +++ b/kairos-cli/src/client.rs @@ -65,7 +65,7 @@ pub fn deposit>( let deploy = DeployBuilder::new( env!("CASPER_CHAIN_NAME"), deposit_session, - &depositor_secret_key, + depositor_secret_key, ) .with_standard_payment(MAX_GAS_FEE_PAYMENT_AMOUNT) // max amount allowed to be used on gas fees .with_timestamp(Timestamp::now()) diff --git a/kairos-cli/src/commands/deposit.rs b/kairos-cli/src/commands/deposit.rs index bf690cfb..6b40c7e0 100644 --- a/kairos-cli/src/commands/deposit.rs +++ b/kairos-cli/src/commands/deposit.rs @@ -24,7 +24,7 @@ pub fn run(args: Args, kairos_server_address: Url) -> Result { let amount: u64 = args.amount.field; let path = args.private_key_path.field; let depositor_secret_key = - SecretKey::from_file(&path).map_err(|err| CryptoError::FailedToParseKey { + SecretKey::from_file(path).map_err(|err| CryptoError::FailedToParseKey { error: err.to_string(), })?; From 524df7b16175d0e5a7ba45077cbe41369b3849c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Fri, 7 Jun 2024 14:04:25 +0200 Subject: [PATCH 26/40] kairos-cli/tests: use updated cctl utils --- kairos-cli/Cargo.toml | 5 +---- kairos-cli/src/common/args.rs | 1 + kairos-cli/tests/cli_tests.rs | 7 +------ 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index 369c6958..5049a2cb 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -19,7 +19,7 @@ cctl-tests = [] [dependencies] casper-client.workspace = true -casper-types = { version = "4", features = ["std-fs-io"] } +casper-client-types = { workspace = true, features = ["std"] } # TODO: Change `std` -> `std-fs-io` in the future version. clap = { version = "4", features = ["derive", "deprecated"] } hex = "0.4" thiserror = "1" @@ -30,7 +30,6 @@ axum-extra = { version = "0.9", features = [ "typed-routing" ] } reqwest = { version = "0.12", features = ["blocking", "json"] } serde_json = "1.0" serde = "1.0" -thiserror = "1" [dev-dependencies] tokio = "1" @@ -38,5 +37,3 @@ assert_cmd = "2" predicates = "3" kairos-test-utils = { path = "../kairos-test-utils" } casper-client-hashing.workspace = true -casper-hashing.workspace = true -casper-client-hashing.workspace = true diff --git a/kairos-cli/src/common/args.rs b/kairos-cli/src/common/args.rs index d9ff7501..4ea690e1 100644 --- a/kairos-cli/src/common/args.rs +++ b/kairos-cli/src/common/args.rs @@ -20,6 +20,7 @@ pub struct NonceArg { pub val: u64, } +#[derive(Args, Debug)] pub struct ContractHashArg { #[arg(id = "contract-hash", long, short = 'c', value_name = "CONTRACT_HASH")] pub field: String, diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index bc73070b..a911b349 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -1,5 +1,6 @@ use assert_cmd::Command; use reqwest::Url; +use std::fs; use std::path::PathBuf; use casper_client::types::DeployHash; @@ -25,11 +26,6 @@ async fn deposit_successful_with_ed25519() { .expect("Expected at least one node after successful network run"); let casper_rpc_url = Url::parse(&format!("http://localhost:{}/rpc", node.port.rpc_port)).unwrap(); - let casper_sse_url = Url::parse(&format!( - "http://localhost:{}/events/main", - node.port.sse_port - )) - .unwrap(); let kairos = kairos::Kairos::run(casper_rpc_url, None).await.unwrap(); @@ -43,7 +39,6 @@ async fn deposit_successful_with_ed25519() { .arg(kairos.url.as_str()) .arg("deposit") .arg("--contract-hash") - .arg("000000000000000000000000000000000000") .arg("--amount") .arg("123") .arg("--private-key") From 60e16c7afed1e691123cf4779f5cbad7c3afe985 Mon Sep 17 00:00:00 2001 From: Avi Dessauer Date: Tue, 25 Jun 2024 12:00:46 -0500 Subject: [PATCH 27/40] Fixup --- kairos-cli/tests/cli_tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index a911b349..0a7732ce 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -1,6 +1,5 @@ use assert_cmd::Command; use reqwest::Url; -use std::fs; use std::path::PathBuf; use casper_client::types::DeployHash; From c54f18b782f32ed2ef59a270e66475c5671db593 Mon Sep 17 00:00:00 2001 From: Avi Dessauer Date: Tue, 25 Jun 2024 12:11:53 -0500 Subject: [PATCH 28/40] Refactor deposit client --- kairos-cli/src/client.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/kairos-cli/src/client.rs b/kairos-cli/src/client.rs index 11529d91..6dc8512a 100644 --- a/kairos-cli/src/client.rs +++ b/kairos-cli/src/client.rs @@ -1,7 +1,9 @@ +use axum_extra::routing::TypedPath; use casper_client::types::DeployHash; use casper_client::types::{DeployBuilder, ExecutableDeployItem, TimeDiff, Timestamp}; use casper_client_types::{crypto::SecretKey, runtime_args, ContractHash, RuntimeArgs, U512}; -use reqwest::{blocking, Url}; +use kairos_server::routes::deposit::DepositPath; +use reqwest::Url; use serde::{Deserialize, Serialize}; use std::fmt; use std::fs; @@ -43,13 +45,12 @@ impl From for KairosClientError { } } -pub fn deposit>( +pub fn deposit( base_url: &Url, depositor_secret_key: &SecretKey, contract_hash: &ContractHash, - amount: A, + amount: impl Into, ) -> Result { - // TODO let deposit_session_wasm_path = Path::new(env!("PATH_TO_SESSION_BINARIES")).join("deposit-session-optimized.wasm"); let deposit_session_wasm_bytes = fs::read(&deposit_session_wasm_path).unwrap_or_else(|err| { @@ -73,20 +74,19 @@ pub fn deposit>( .build() .map_err(|err| KairosClientError::CasperClientError(err.to_string()))?; - let client = blocking::Client::new(); - let url = base_url.join("/api/v1/deposit").unwrap(); - let response = client - .post(url) + let response = reqwest::blocking::Client::new() + .post(base_url.join(DepositPath::PATH).unwrap()) .header("Content-Type", "application/json") .json(&deploy) .send() - .map_err(Into::::into)?; + .map_err(KairosClientError::from)?; + let status = response.status(); if !status.is_success() { Err(KairosClientError::KairosServerError(status.to_string())) } else { response .json::() - .map_err(Into::::into) + .map_err(KairosClientError::from) } } From fcd46da1aabef91b92e2d33272db002d05e2aa50 Mon Sep 17 00:00:00 2001 From: Avi Dessauer Date: Tue, 25 Jun 2024 12:57:45 -0500 Subject: [PATCH 29/40] Add recipient to deposit cli command --- kairos-cli/src/client.rs | 10 +++++--- kairos-cli/src/commands/deposit.rs | 5 +++- kairos-cli/src/commands/transfer.rs | 9 ++++--- kairos-cli/src/common/args.rs | 24 +++++++++++++++++++ kairos-cli/tests/cli_tests.rs | 4 ++++ .../contract-utils/src/constants.rs | 2 +- 6 files changed, 44 insertions(+), 10 deletions(-) diff --git a/kairos-cli/src/client.rs b/kairos-cli/src/client.rs index 6dc8512a..c2870428 100644 --- a/kairos-cli/src/client.rs +++ b/kairos-cli/src/client.rs @@ -1,6 +1,5 @@ use axum_extra::routing::TypedPath; -use casper_client::types::DeployHash; -use casper_client::types::{DeployBuilder, ExecutableDeployItem, TimeDiff, Timestamp}; +use casper_client::types::{DeployBuilder, DeployHash, ExecutableDeployItem, TimeDiff, Timestamp}; use casper_client_types::{crypto::SecretKey, runtime_args, ContractHash, RuntimeArgs, U512}; use kairos_server::routes::deposit::DepositPath; use reqwest::Url; @@ -50,6 +49,7 @@ pub fn deposit( depositor_secret_key: &SecretKey, contract_hash: &ContractHash, amount: impl Into, + recipient: casper_client_types::PublicKey, ) -> Result { let deposit_session_wasm_path = Path::new(env!("PATH_TO_SESSION_BINARIES")).join("deposit-session-optimized.wasm"); @@ -61,7 +61,11 @@ pub fn deposit( }); let deposit_session = ExecutableDeployItem::new_module_bytes( deposit_session_wasm_bytes.into(), - runtime_args! { "demo_contract" => *contract_hash, "amount" => amount.into() }, + runtime_args! { + "demo_contract" => *contract_hash, + "amount" => amount.into(), + "recipient" => recipient + }, ); let deploy = DeployBuilder::new( env!("CASPER_CHAIN_NAME"), diff --git a/kairos-cli/src/commands/deposit.rs b/kairos-cli/src/commands/deposit.rs index 6b40c7e0..7fc41c1e 100644 --- a/kairos-cli/src/commands/deposit.rs +++ b/kairos-cli/src/commands/deposit.rs @@ -1,5 +1,5 @@ use crate::client; -use crate::common::args::{AmountArg, ContractHashArg, PrivateKeyPathArg}; +use crate::common::args::{AmountArg, ContractHashArg, PrivateKeyPathArg, RecipientArg}; use crate::error::CliError; use casper_client_types::{crypto::SecretKey, ContractHash}; @@ -17,6 +17,8 @@ pub struct Args { private_key_path: PrivateKeyPathArg, #[clap(flatten)] contract_hash: ContractHashArg, + #[clap(flatten)] + recipient: RecipientArg, } pub fn run(args: Args, kairos_server_address: Url) -> Result { @@ -36,6 +38,7 @@ pub fn run(args: Args, kairos_server_address: Url) -> Result { &depositor_secret_key, &contract_hash, amount, + args.recipient.try_into()?, ) .map_err(Into::::into) .map(|deploy_hash| { diff --git a/kairos-cli/src/commands/transfer.rs b/kairos-cli/src/commands/transfer.rs index 838cbbfd..c9c7ea0c 100644 --- a/kairos-cli/src/commands/transfer.rs +++ b/kairos-cli/src/commands/transfer.rs @@ -1,7 +1,6 @@ use crate::client::KairosClientError; -use crate::common::args::{AmountArg, NonceArg, PrivateKeyPathArg}; +use crate::common::args::{AmountArg, NonceArg, PrivateKeyPathArg, RecipientArg}; use crate::error::CliError; -use crate::utils::parse_hex_string; use axum_extra::routing::TypedPath; use kairos_crypto::error::CryptoError; @@ -16,8 +15,8 @@ use reqwest::Url; #[derive(Parser)] pub struct Args { - #[arg(long, short, value_name = "PUBLIC_KEY", value_parser = parse_hex_string)] - recipient: ::std::vec::Vec, // Absolute path is required here - see https://github.com/clap-rs/clap/issues/4626#issue-1528622454. + #[clap(flatten)] + recipient: RecipientArg, #[clap(flatten)] amount: AmountArg, #[clap(flatten)] @@ -27,7 +26,7 @@ pub struct Args { } pub fn run(args: Args, kairos_server_address: Url) -> Result { - let recipient = Signer::from_public_key(args.recipient)?.to_public_key()?; + let recipient = Signer::from_public_key(args.recipient.recipient)?.to_public_key()?; let amount: u64 = args.amount.field; let signer = Signer::from_private_key_file(args.private_key_path.field).map_err(CryptoError::from)?; diff --git a/kairos-cli/src/common/args.rs b/kairos-cli/src/common/args.rs index 4ea690e1..6c6b2939 100644 --- a/kairos-cli/src/common/args.rs +++ b/kairos-cli/src/common/args.rs @@ -1,6 +1,10 @@ use std::path::PathBuf; +use casper_client_types::bytesrepr::FromBytes; use clap::Args; +use kairos_crypto::error::CryptoError; + +use crate::utils::parse_hex_string; #[derive(Args, Debug)] pub struct AmountArg { @@ -25,3 +29,23 @@ pub struct ContractHashArg { #[arg(id = "contract-hash", long, short = 'c', value_name = "CONTRACT_HASH")] pub field: String, } + +#[derive(Args, Debug)] +pub struct RecipientArg { + #[arg(long, short, value_name = "PUBLIC_KEY", value_parser = parse_hex_string)] + pub recipient: ::std::vec::Vec, // Absolute path is required here - see https://github.com/clap-rs/clap/issues/4626#issue-1528622454. +} + +impl TryFrom for casper_client_types::PublicKey { + type Error = CryptoError; + + fn try_from(arg: RecipientArg) -> Result { + let (pk, _) = casper_client_types::PublicKey::from_bytes(&arg.recipient).map_err(|_| { + CryptoError::FailedToParseKey { + error: format!("invalid public key: {}", hex::encode(&arg.recipient)), + } + })?; + + Ok(pk) + } +} diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index af5d56e5..514e0689 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -125,6 +125,8 @@ fn deposit_invalid_private_key_path() { .arg("000000000000000000000000000000000000") .arg("--amount") .arg("123") + .arg("--recipient") + .arg("01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356") // Example recipient .arg("--private-key") .arg(secret_key_path); cmd.assert() @@ -142,6 +144,8 @@ fn deposit_invalid_private_key_content() { .arg("000000000000000000000000000000000000") .arg("--amount") .arg("123") + .arg("--recipient") + .arg("01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356") // Example recipient .arg("--private-key") .arg(secret_key_path); cmd.assert() diff --git a/kairos-contracts/demo-contract/contract-utils/src/constants.rs b/kairos-contracts/demo-contract/contract-utils/src/constants.rs index 35b5c422..fe508d3d 100644 --- a/kairos-contracts/demo-contract/contract-utils/src/constants.rs +++ b/kairos-contracts/demo-contract/contract-utils/src/constants.rs @@ -10,7 +10,7 @@ pub const RUNTIME_ARG_INITIAL_TRIE_ROOT: &str = "initial_trie_root"; pub const RUNTIME_ARG_TEMP_PURSE: &str = "temp_purse"; pub const RUNTIME_ARG_AMOUNT: &str = "amount"; pub const RUNTIME_ARG_RECEIPT: &str = "risc0_receipt"; -pub const RUNTIME_ARG_RECIPIENT: &str = "risc0_recipient"; +pub const RUNTIME_ARG_RECIPIENT: &str = "recipient"; pub const EP_INIT_NAME: &str = "init"; pub const EP_GET_PURSE_NAME: &str = "get_purse"; From d33cc2850fcc0cacf4ff6a1b94c73f5f8b5064ae Mon Sep 17 00:00:00 2001 From: Avi Dessauer Date: Tue, 25 Jun 2024 13:57:26 -0500 Subject: [PATCH 30/40] Make tests pass --- .../tests/integration_tests.rs | 15 ++++++++------ demo-contract-tests/tests/test_fixture/mod.rs | 18 ++++++++++------- kairos-cli/tests/cli_tests.rs | 20 +++++++++++++++++-- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/demo-contract-tests/tests/integration_tests.rs b/demo-contract-tests/tests/integration_tests.rs index f9113f3b..d1ced07f 100644 --- a/demo-contract-tests/tests/integration_tests.rs +++ b/demo-contract-tests/tests/integration_tests.rs @@ -15,7 +15,8 @@ mod tests { let mut fixture = TestContext::new(None); let user = fixture.create_funded_user(); - let user_balance_before = fixture.get_user_balance(user); + let user_account_hash = user.to_account_hash(); + let user_balance_before = fixture.get_user_balance(user_account_hash); // check that the contract balance is zero before depositing let deposit_amount = U512::from(100000000000u64); @@ -29,7 +30,7 @@ mod tests { let contract_balance_after = fixture.get_contract_balance(); assert_eq!(contract_balance_after, deposit_amount); - let user_balance_after = fixture.get_user_balance(user); + let user_balance_after = fixture.get_user_balance(user_account_hash); assert!(user_balance_after <= user_balance_before - deposit_amount); } @@ -38,10 +39,11 @@ mod tests { let mut fixture = TestContext::new(None); let user = fixture.create_funded_user(); + let user_account_hash = user.to_account_hash(); let amount = U512::from(100000000000u64); fixture.deposit_succeeds(user, amount); - fixture.transfer_from_contract_purse_to_user_fails(user, amount) + fixture.transfer_from_contract_purse_to_user_fails(user_account_hash, amount) } #[test] @@ -50,7 +52,7 @@ mod tests { let user = fixture.create_funded_user(); let amount = U512::from(100000000000u64); - fixture.deposit_succeeds(user, amount); + fixture.deposit_succeeds(user.clone(), amount); fixture.transfer_from_contract_purse_to_user_fails(fixture.admin, amount) } @@ -59,10 +61,11 @@ mod tests { fn test_transfer_from_contract_purse_by_uref_to_user_fails() { let mut fixture = TestContext::new(None); let user = fixture.create_funded_user(); + let user_account_hash = user.to_account_hash(); let amount = U512::from(100000000000u64); - fixture.deposit_succeeds(user, amount); + fixture.deposit_succeeds(user.clone(), amount); - fixture.transfer_from_contract_purse_by_uref_to_user_fails(user, amount) + fixture.transfer_from_contract_purse_by_uref_to_user_fails(user_account_hash, amount) } #[test] diff --git a/demo-contract-tests/tests/test_fixture/mod.rs b/demo-contract-tests/tests/test_fixture/mod.rs index 09469e07..103ab93d 100644 --- a/demo-contract-tests/tests/test_fixture/mod.rs +++ b/demo-contract-tests/tests/test_fixture/mod.rs @@ -35,7 +35,8 @@ impl TestContext { let mut builder = InMemoryWasmTestBuilder::default(); builder.run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); - let admin = create_funded_account_for_secret_key_bytes(&mut builder, ADMIN_SECRET_KEY); + let admin = create_funded_account_for_secret_key_bytes(&mut builder, ADMIN_SECRET_KEY) + .to_account_hash(); let contract_path = get_wasm_directory().0.join("demo-contract-optimized.wasm"); run_session_with_args( &mut builder, @@ -71,7 +72,7 @@ impl TestContext { } } - pub fn create_funded_user(&mut self) -> AccountHash { + pub fn create_funded_user(&mut self) -> PublicKey { let mut random_secret_key: [u8; 32] = rand::random(); while random_secret_key == ADMIN_SECRET_KEY { random_secret_key = rand::random(); @@ -88,18 +89,21 @@ impl TestContext { self.builder.get_purse_balance(self.contract_purse) } - pub fn deposit_succeeds(&mut self, depositor: AccountHash, amount: U512) { + pub fn deposit_succeeds(&mut self, depositor: PublicKey, amount: U512) { + let account_hash = depositor.to_account_hash(); + let deposit_session_path = get_wasm_directory() .1 .join("deposit-session-optimized.wasm"); let session_args = runtime_args! { "amount" => amount, - "demo_contract" => self.contract_hash + "demo_contract" => self.contract_hash, + "recipient" => depositor, }; run_session_with_args( &mut self.builder, deposit_session_path.as_path(), - depositor, + account_hash, session_args, ); self.builder.expect_success(); @@ -181,7 +185,7 @@ pub fn run_session_with_args( pub fn create_funded_account_for_secret_key_bytes( builder: &mut WasmTestBuilder, account_secret_key_bytes: [u8; 32], -) -> AccountHash { +) -> PublicKey { let account_secret_key = SecretKey::ed25519_from_bytes(account_secret_key_bytes).unwrap(); let account_public_key = PublicKey::from(&account_secret_key); let account_hash = account_public_key.to_account_hash(); @@ -195,5 +199,5 @@ pub fn create_funded_account_for_secret_key_bytes( ) .build(); builder.exec(transfer).expect_success().commit(); - account_hash + account_public_key } diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index 514e0689..4b395374 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -1,10 +1,14 @@ use assert_cmd::Command; +use casper_client_types::{runtime_args, RuntimeArgs}; use reqwest::Url; use std::path::PathBuf; use casper_client::types::DeployHash; use casper_client_hashing::Digest; -use kairos_test_utils::{cctl, kairos}; +use kairos_test_utils::{ + cctl::{CCTLNetwork, DeployableContract}, + kairos, +}; // Helper function to get the path to a fixture file fn fixture_path(relative_path: &str) -> PathBuf { @@ -16,9 +20,20 @@ fn fixture_path(relative_path: &str) -> PathBuf { #[tokio::test] #[cfg_attr(not(feature = "cctl-tests"), ignore)] async fn deposit_successful_with_ed25519() { - let network = cctl::CCTLNetwork::run(None, None, None, None) + let contract_wasm_path = + PathBuf::from(env!("PATH_TO_WASM_BINARIES")).join("demo-contract-optimized.wasm"); + let hash_name = "kairos_contract_package_hash"; + let contract_to_deploy = DeployableContract { + hash_name: hash_name.to_string(), + runtime_args: runtime_args! { "initial_trie_root" => Option::<[u8; 32]>::None }, + path: contract_wasm_path, + }; + let network = CCTLNetwork::run(None, Some(contract_to_deploy), None, None) .await .unwrap(); + + let contract_hash = network.get_contract_hash_for(hash_name); + let node = network .nodes .first() @@ -38,6 +53,7 @@ async fn deposit_successful_with_ed25519() { .arg(kairos.url.as_str()) .arg("deposit") .arg("--contract-hash") + .arg(contract_hash.to_string()) .arg("--amount") .arg("123") .arg("--private-key") From 22de92a1b4aa338745124379afc832d05920127b Mon Sep 17 00:00:00 2001 From: Avi Dessauer Date: Tue, 25 Jun 2024 17:40:39 -0500 Subject: [PATCH 31/40] CCTL will not start --- Cargo.lock | 2 ++ flake.nix | 16 ++++++++++ kairos-cli/Cargo.toml | 6 ++++ kairos-cli/bin/main.rs | 6 ++++ kairos-cli/src/commands/mod.rs | 2 ++ kairos-cli/src/commands/run_cctl.rs | 45 +++++++++++++++++++++++++++++ kairos-cli/src/lib.rs | 6 ++++ kairos-server/src/main.rs | 10 +++---- kairos-test-utils/src/cctl.rs | 2 ++ 9 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 kairos-cli/src/commands/run_cctl.rs diff --git a/Cargo.lock b/Cargo.lock index c45151e0..51f7c9de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2485,6 +2485,8 @@ dependencies = [ "serde_json", "thiserror", "tokio", + "tracing", + "tracing-subscriber", ] [[package]] diff --git a/flake.nix b/flake.nix index 75a86530..a5de7fed 100644 --- a/flake.nix +++ b/flake.nix @@ -47,6 +47,18 @@ ]; craneLib = inputs.crane.lib.${system}.overrideToolchain rustToolchain; + # TODO reuse in nixos tests + cctlConfig = { + chainspec = pkgs.fetchurl { + url = "https://raw.githubusercontent.com/cspr-rad/casper-node/53136ac5f004f2ae70a75b4eeb2ff7d907aff6aa/resources/local/chainspec.toml.in"; + hash = "sha256-b/6c5o3JXFlaTgTHxs8JepaHzjMG75knzlKKqRd/7pc="; + }; + config = pkgs.fetchurl { + url = "https://raw.githubusercontent.com/cspr-rad/casper-node/53136ac5f004f2ae70a75b4eeb2ff7d907aff6aa/resources/local/config.toml"; + hash = "sha256-ZuNbxw0nBjuONEZRK8Ru96zZQak4MEQ/eM1fA6esyCM="; + }; + }; + kairosContractsAttrs = { src = lib.cleanSourceWith { src = lib.fileset.toSource { @@ -149,6 +161,8 @@ CASPER_CHAIN_NAME = "cspr-dev-cctl"; PATH_TO_WASM_BINARIES = "${self'.packages.kairos-contracts}/bin"; PATH_TO_SESSION_BINARIES = "${self'.packages.kairos-session-code}/bin"; + CCTL_CONFIG = "${cctlConfig.config}"; + CCTL_CHAINSPEC = "${cctlConfig.chainspec}"; meta.mainProgram = "kairos-server"; }; @@ -161,6 +175,8 @@ CASPER_CHAIN_NAME = "cspr-dev-cctl"; PATH_TO_WASM_BINARIES = "${self'.packages.kairos-contracts}/bin"; PATH_TO_SESSION_BINARIES = "${self'.packages.kairos-session-code}/bin"; + CCTL_CONFIG = "${cctlConfig.config}"; + CCTL_CHAINSPEC = "${cctlConfig.chainspec}"; inputsFrom = [ self'.packages.kairos self'.packages.kairos-contracts ]; }; diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index 5049a2cb..7d85cde4 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -14,10 +14,14 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] +default = ["demo"] all-tests = ["cctl-tests"] cctl-tests = [] +demo = ["dep:kairos-test-utils", "dep:tokio"] [dependencies] +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] } casper-client.workspace = true casper-client-types = { workspace = true, features = ["std"] } # TODO: Change `std` -> `std-fs-io` in the future version. clap = { version = "4", features = ["derive", "deprecated"] } @@ -30,6 +34,8 @@ axum-extra = { version = "0.9", features = [ "typed-routing" ] } reqwest = { version = "0.12", features = ["blocking", "json"] } serde_json = "1.0" serde = "1.0" +kairos-test-utils = { path = "../kairos-test-utils", optional = true } +tokio = { version = "1", features = ["full"], optional = true } [dev-dependencies] tokio = "1" diff --git a/kairos-cli/bin/main.rs b/kairos-cli/bin/main.rs index bf49f34a..b9253099 100644 --- a/kairos-cli/bin/main.rs +++ b/kairos-cli/bin/main.rs @@ -1,7 +1,13 @@ use clap::Parser; use std::process; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; fn main() { + tracing_subscriber::registry() + .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| "warn".into())) + .with(tracing_subscriber::fmt::layer()) + .init(); + let args = kairos_cli::Cli::parse(); match kairos_cli::run(args) { Ok(output) => { diff --git a/kairos-cli/src/commands/mod.rs b/kairos-cli/src/commands/mod.rs index 13010da8..e917a1f7 100644 --- a/kairos-cli/src/commands/mod.rs +++ b/kairos-cli/src/commands/mod.rs @@ -1,3 +1,5 @@ pub mod deposit; +#[cfg(feature = "demo")] +pub mod run_cctl; pub mod transfer; pub mod withdraw; diff --git a/kairos-cli/src/commands/run_cctl.rs b/kairos-cli/src/commands/run_cctl.rs new file mode 100644 index 00000000..0a53cf08 --- /dev/null +++ b/kairos-cli/src/commands/run_cctl.rs @@ -0,0 +1,45 @@ +use std::path::{Path, PathBuf}; + +use casper_client_types::{runtime_args, RuntimeArgs}; +use kairos_test_utils::cctl::{CCTLNetwork, DeployableContract}; + +use crate::error::CliError; + +pub fn run() -> Result { + tokio::runtime::Runtime::new().unwrap().block_on(async { + let contract_wasm_path = + PathBuf::from(env!("PATH_TO_WASM_BINARIES")).join("demo-contract-optimized.wasm"); + let hash_name = "kairos_contract_package_hash"; + let contract_to_deploy = DeployableContract { + hash_name: hash_name.to_string(), + runtime_args: runtime_args! { "initial_trie_root" => Option::<[u8; 32]>::None }, + path: contract_wasm_path, + }; + println!("Deploying contract..."); + let chainspec_path = Path::new(env!("CCTL_CHAINSPEC")); + let config_path = Path::new(env!("CCTL_CONFIG")); + + let network = CCTLNetwork::run(None, Some(contract_to_deploy), Some(chainspec_path), Some(config_path)) + .await + .unwrap(); + + println!("Contract deployed successfully!"); + let contract_hash = network.get_contract_hash_for(hash_name); + + let node = network + .nodes + .first() + .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 KAIROS_SERVER_CASPER_RPC={}", casper_rpc_url); + + let _ = tokio::signal::ctrl_c().await; + }); + + Ok("exiting".to_string()) +} diff --git a/kairos-cli/src/lib.rs b/kairos-cli/src/lib.rs index 2d56b0a0..6e9e2c30 100644 --- a/kairos-cli/src/lib.rs +++ b/kairos-cli/src/lib.rs @@ -26,6 +26,9 @@ pub enum Command { Transfer(commands::transfer::Args), #[command(about = "Withdraws funds from your account")] Withdraw(commands::withdraw::Args), + + #[cfg(feature = "demo")] + RunDemoCctl, } pub fn run( @@ -38,5 +41,8 @@ pub fn run( Command::Deposit(args) => commands::deposit::run(args, kairos_server_address), Command::Transfer(args) => commands::transfer::run(args, kairos_server_address), Command::Withdraw(args) => commands::withdraw::run(args, kairos_server_address), + + #[cfg(feature = "demo")] + Command::RunDemoCctl => commands::run_cctl::run(), } } diff --git a/kairos-server/src/main.rs b/kairos-server/src/main.rs index 107e421d..c37b58c9 100644 --- a/kairos-server/src/main.rs +++ b/kairos-server/src/main.rs @@ -1,13 +1,13 @@ use dotenvy::dotenv; use kairos_server::config::ServerConfig; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; #[tokio::main] async fn main() { - let subscriber = tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - .finish(); - - tracing::subscriber::set_global_default(subscriber).expect("Failed to set subscriber"); + tracing_subscriber::registry() + .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into())) + .with(tracing_subscriber::fmt::layer()) + .init(); // loads the environment from the current directories .env file // if the .env does not exist in the current directory, diff --git a/kairos-test-utils/src/cctl.rs b/kairos-test-utils/src/cctl.rs index 3f8b52a9..cd6f2c62 100644 --- a/kairos-test-utils/src/cctl.rs +++ b/kairos-test-utils/src/cctl.rs @@ -70,6 +70,7 @@ impl CCTLNetwork { }) .unwrap_or(tempdir()?.into_path()); let assets_dir = working_dir.join("assets"); + tracing::info!("Working directory: {:?}", working_dir); let mut setup_command = Command::new("cctl-infra-net-setup"); setup_command.env("CCTL_ASSETS", &assets_dir); @@ -82,6 +83,7 @@ impl CCTLNetwork { setup_command.arg(format!("config={}", config_path.to_str().unwrap())); }; + tracing::info!("Setting up network configuration"); let output = setup_command .output() .expect("Failed to setup network configuration"); From 181f794f5de91d335dd6414af1ae95810b3311c4 Mon Sep 17 00:00:00 2001 From: Avi Dessauer Date: Tue, 25 Jun 2024 19:44:26 -0500 Subject: [PATCH 32/40] Fix kairos-test-utils cctl spinup --- kairos-test-utils/src/cctl.rs | 98 ++++++++----------- ...st_cctl_deploys_a_contract_successfully.rs | 51 ++++++++++ ...test_cctl_network_starts_and_terminates.rs | 38 +++++++ 3 files changed, 131 insertions(+), 56 deletions(-) create mode 100644 kairos-test-utils/tests/test_cctl_deploys_a_contract_successfully.rs create mode 100644 kairos-test-utils/tests/test_cctl_network_starts_and_terminates.rs diff --git a/kairos-test-utils/src/cctl.rs b/kairos-test-utils/src/cctl.rs index cd6f2c62..aa04c30a 100644 --- a/kairos-test-utils/src/cctl.rs +++ b/kairos-test-utils/src/cctl.rs @@ -11,11 +11,11 @@ use casper_client_types::{ExecutionResult, Key, PublicKey, RuntimeArgs, SecretKe use casper_types::ContractHash; use hex::FromHex; use rand::Rng; -use std::fs; use std::io::{self, Write}; use std::path::Path; use std::path::PathBuf; use std::process::Command; +use std::{fs, time::Instant}; use tempfile::tempdir; #[derive(Debug, PartialEq, Clone, Copy)] @@ -56,12 +56,21 @@ pub struct DeployableContract { pub const MAX_GAS_FEE_PAYMENT_AMOUNT: u64 = 10_000_000_000_000; impl CCTLNetwork { + /// Spins up a CCTL network, and deploys a contract if provided + /// + /// If a chain spec and config path are not provided, the environment variables `CCTL_CHAINSPEC` and `CCTL_CONFIG` are used. + /// + /// WARNING: do not use this function in unit tests, only sequentially executed integration tests. + /// Ensure that two instances of this function are not running at the same time even in different processes. pub async fn run( working_dir: Option, contract_to_deploy: Option, chainspec_path: Option<&Path>, config_path: Option<&Path>, ) -> anyhow::Result { + let chainspec_path = chainspec_path.unwrap_or_else(|| Path::new(env!("CCTL_CHAINSPEC"))); + let config_path = config_path.unwrap_or_else(|| Path::new(env!("CCTL_CONFIG"))); + let working_dir = working_dir .map(|dir| { std::fs::create_dir_all(&dir) @@ -75,13 +84,9 @@ impl CCTLNetwork { let mut setup_command = Command::new("cctl-infra-net-setup"); setup_command.env("CCTL_ASSETS", &assets_dir); - if let Some(chainspec_path) = chainspec_path { - setup_command.arg(format!("chainspec={}", chainspec_path.to_str().unwrap())); - }; + setup_command.arg(format!("chainspec={}", chainspec_path.to_str().unwrap())); - if let Some(config_path) = config_path { - setup_command.arg(format!("config={}", config_path.to_str().unwrap())); - }; + setup_command.arg(format!("config={}", config_path.to_str().unwrap())); tracing::info!("Setting up network configuration"); let output = setup_command @@ -129,11 +134,23 @@ impl CCTLNetwork { let node_port = nodes.first().unwrap().port.rpc_port; let casper_node_rpc_url = format!("http://localhost:{}/rpc", node_port); + let start_time = Instant::now(); tracing::info!("Waiting for network to pass genesis"); retry(ExponentialBackoff::default(), || async { + // This prevents retrying forever even after ctrl-c + let timed_out = start_time.elapsed().as_secs() > 60; + get_node_status(JsonRpcId::Number(1), &casper_node_rpc_url, Verbosity::Low) .await + .map_err(|err| { + let elapsed = start_time.elapsed().as_secs(); + tracing::info!("Running for {elapsed}s, Error: {err:?}"); + err + }) .map_err(|err| match &err { + err if timed_out => { + backoff::Error::permanent(anyhow!("Timeout on error: {err:?}")) + } Error::ResponseIsHttpError { .. } | Error::FailedToGetResponse { .. } => { backoff::Error::transient(anyhow!(err)) } @@ -141,6 +158,10 @@ impl CCTLNetwork { }) .map(|success| match success.result.reactor_state { ReactorState::Validate => Ok(()), + // _ if timed_out => Ok(()), + rs if timed_out => Err(backoff::Error::permanent(anyhow!( + "Node didn't reach the VALIDATE state before timeout: {rs:?}" + ))), _ => Err(backoff::Error::transient(anyhow!( "Node didn't reach the VALIDATE state yet" ))), @@ -256,7 +277,10 @@ async fn deploy_contract( })?; tracing::info!("Waiting for successful contract initialization"); + let start = Instant::now(); retry(ExponentialBackoff::default(), || async { + let timed_out = start.elapsed().as_secs() > 60; + let expected_rpc_id = JsonRpcId::Number(rand::thread_rng().gen::()); let response = get_deploy( expected_rpc_id.clone(), @@ -266,12 +290,19 @@ async fn deploy_contract( false, ) .await + .map_err(|err| { + let elapsed = start.elapsed().as_secs(); + tracing::info!("Running for {elapsed}s, Error: {err:?}"); + err + }) .map_err(|err| match &err { + e if timed_out => backoff::Error::permanent(anyhow!("Timeout on error: {e:?}")), Error::ResponseIsHttpError { .. } | Error::FailedToGetResponse { .. } => { backoff::Error::transient(anyhow!(err)) } _ => backoff::Error::permanent(anyhow!(err)), })?; + if response.id == expected_rpc_id { match response.result.execution_results.first() { Some(result) => match &result.result { @@ -280,7 +311,10 @@ async fn deploy_contract( } ExecutionResult::Success { .. } => Ok(()), }, - Option::None => Err(backoff::Error::transient(anyhow!( + None if timed_out => Err(backoff::Error::permanent(anyhow!( + "Timeout on error: No execution results" + ))), + None => Err(backoff::Error::transient(anyhow!( "No execution results there yet" ))), } @@ -370,51 +404,3 @@ async fn deploy_contract( contract_hash, )) } - -#[cfg(test)] -mod tests { - use super::*; - use casper_client_types::runtime_args; - use hex::FromHex; - - #[cfg_attr(not(feature = "cctl-tests"), ignore)] - #[tokio::test] - async fn test_cctl_network_starts_and_terminates() { - let network = CCTLNetwork::run(None, None, None, None).await.unwrap(); - for node in &network.nodes { - if node.state == NodeState::Running { - let node_status = get_node_status( - JsonRpcId::Number(1), - &format!("http://localhost:{}", node.port.rpc_port), - Verbosity::High, - ) - .await - .unwrap(); - assert_eq!(node_status.result.reactor_state, ReactorState::Validate); - } - } - } - - #[cfg_attr(not(feature = "cctl-tests"), ignore)] - #[tokio::test] - async fn test_cctl_deploys_a_contract_successfully() { - let contract_wasm_path = - PathBuf::from(env!("PATH_TO_WASM_BINARIES")).join("demo-contract-optimized.wasm"); - let hash_name = "kairos_contract_package_hash"; - let contract_to_deploy = DeployableContract { - hash_name: hash_name.to_string(), - runtime_args: runtime_args! { "initial_trie_root" => Option::<[u8; 32]>::None }, - path: contract_wasm_path, - }; - let network = CCTLNetwork::run(None, Some(contract_to_deploy), None, None) - .await - .unwrap(); - let expected_contract_hash_path = network.working_dir.join("contracts").join(hash_name); - assert!(expected_contract_hash_path.exists()); - - let hash_string = fs::read_to_string(expected_contract_hash_path).unwrap(); - let contract_hash_bytes = <[u8; 32]>::from_hex(hash_string).unwrap(); - let contract_hash = ContractHash::new(contract_hash_bytes); - assert!(contract_hash.to_formatted_string().starts_with("contract-")) - } -} diff --git a/kairos-test-utils/tests/test_cctl_deploys_a_contract_successfully.rs b/kairos-test-utils/tests/test_cctl_deploys_a_contract_successfully.rs new file mode 100644 index 00000000..441130b9 --- /dev/null +++ b/kairos-test-utils/tests/test_cctl_deploys_a_contract_successfully.rs @@ -0,0 +1,51 @@ +use casper_types::ContractHash; +use hex::FromHex; +use kairos_test_utils::cctl::CCTLNetwork; +use std::fs; +use std::path::Path; +use std::path::PathBuf; + +use casper_client_types::{runtime_args, RuntimeArgs}; +use kairos_test_utils::cctl::DeployableContract; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; + +fn tracing_init() { + let _ = tracing_subscriber::registry() + .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into())) + .with(tracing_subscriber::fmt::layer()) + .try_init(); +} + +#[cfg_attr(not(feature = "cctl-tests"), ignore)] +#[tokio::test] +async fn test_cctl_deploys_a_contract_successfully() { + tracing_init(); + + let contract_wasm_path = + PathBuf::from(env!("PATH_TO_WASM_BINARIES")).join("demo-contract-optimized.wasm"); + let hash_name = "kairos_contract_package_hash"; + let contract_to_deploy = DeployableContract { + hash_name: hash_name.to_string(), + runtime_args: runtime_args! { "initial_trie_root" => Option::<[u8; 32]>::None }, + path: contract_wasm_path, + }; + + let chainspec = Path::new(env!("CCTL_CHAINSPEC")); + let config = Path::new(env!("CCTL_CONFIG")); + + let network = CCTLNetwork::run( + None, + Some(contract_to_deploy), + Some(chainspec), + Some(config), + ) + .await + .unwrap(); + let expected_contract_hash_path = network.working_dir.join("contracts").join(hash_name); + assert!(expected_contract_hash_path.exists()); + + let hash_string = fs::read_to_string(expected_contract_hash_path).unwrap(); + let contract_hash_bytes = <[u8; 32]>::from_hex(hash_string).unwrap(); + let contract_hash = ContractHash::new(contract_hash_bytes); + assert!(contract_hash.to_formatted_string().starts_with("contract-")) +} diff --git a/kairos-test-utils/tests/test_cctl_network_starts_and_terminates.rs b/kairos-test-utils/tests/test_cctl_network_starts_and_terminates.rs new file mode 100644 index 00000000..bd4ca319 --- /dev/null +++ b/kairos-test-utils/tests/test_cctl_network_starts_and_terminates.rs @@ -0,0 +1,38 @@ +use std::path::Path; + +use casper_client::{get_node_status, rpcs::results::ReactorState, JsonRpcId, Verbosity}; +use kairos_test_utils::cctl::{CCTLNetwork, NodeState}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; + +fn tracing_init() { + let _ = tracing_subscriber::registry() + .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into())) + .with(tracing_subscriber::fmt::layer()) + .try_init(); +} + +#[cfg_attr(not(feature = "cctl-tests"), ignore)] +#[tokio::test] +async fn test_cctl_network_starts_and_terminates() { + tracing_init(); + + let chainspec = Path::new(env!("CCTL_CHAINSPEC")); + let config = Path::new(env!("CCTL_CONFIG")); + + let network = CCTLNetwork::run(None, None, Some(chainspec), Some(config)) + .await + .unwrap(); + + for node in &network.nodes { + if node.state == NodeState::Running { + let node_status = get_node_status( + JsonRpcId::Number(1), + &format!("http://localhost:{}", node.port.rpc_port), + Verbosity::High, + ) + .await + .unwrap(); + assert_eq!(node_status.result.reactor_state, ReactorState::Validate); + } + } +} From cf91fc089d4b9cc8b2ad03db999ce35dcad9aba9 Mon Sep 17 00:00:00 2001 From: Avi Dessauer Date: Tue, 25 Jun 2024 20:12:29 -0500 Subject: [PATCH 33/40] Fix tests --- kairos-cli/tests/cli_tests.rs | 40 +++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index 4b395374..926f8585 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -20,6 +20,10 @@ fn fixture_path(relative_path: &str) -> PathBuf { #[tokio::test] #[cfg_attr(not(feature = "cctl-tests"), ignore)] async fn deposit_successful_with_ed25519() { + if std::env::var("RUST_LOG").is_ok() { + panic!("RUST_LOG is set, which will interfere with the test"); + } + let contract_wasm_path = PathBuf::from(env!("PATH_TO_WASM_BINARIES")).join("demo-contract-optimized.wasm"); let hash_name = "kairos_contract_package_hash"; @@ -48,6 +52,11 @@ async fn deposit_successful_with_ed25519() { .working_dir .join("assets/users/user-1/secret_key.pem"); + let recipient_pk_path = network + .working_dir + .join("assets/users/user-2/public_key_hex"); + let recipient_pk = std::fs::read_to_string(recipient_pk_path).unwrap(); + let mut cmd = Command::cargo_bin("kairos-cli").unwrap(); cmd.arg("--kairos-server-address") .arg(kairos.url.as_str()) @@ -57,7 +66,10 @@ async fn deposit_successful_with_ed25519() { .arg("--amount") .arg("123") .arg("--private-key") - .arg(depositor_secret_key_path); + .arg(depositor_secret_key_path) + .arg("--recipient") + .arg(recipient_pk); + cmd.assert() .success() .stdout(predicates::function::function(|stdout: &str| { @@ -180,8 +192,32 @@ fn transfer_invalid_recipient() { .arg("--amount") .arg("123") .arg("--private-key") - .arg(secret_key_path); + .arg(secret_key_path) + .arg("--nonce") + .arg("0"); + cmd.assert() .failure() .stderr(predicates::str::contains("failed to parse hex string")); } + +#[test] +fn transfer_valid_recipient() { + let secret_key_path = fixture_path("ed25519/secret_key.pem"); + let hex_pk = "01e8e1e0b10972e4945d1e493d41be8f39f47bb1299f3248f297d22cbc02010f89"; + + let mut cmd = Command::cargo_bin("kairos-cli").unwrap(); + cmd.arg("transfer") + .arg("--recipient") + .arg(hex_pk) + .arg("--amount") + .arg("123") + .arg("--private-key") + .arg(secret_key_path) + .arg("--nonce") + .arg("0"); + + cmd.assert() + .failure() + .stderr(predicates::str::contains("http client error")); +} From 07406fe971cccbd6655851f7707358e96b2f62df Mon Sep 17 00:00:00 2001 From: Avi Dessauer Date: Tue, 25 Jun 2024 21:40:30 -0500 Subject: [PATCH 34/40] Demo stuff --- .env | 1 + kairos-server/.env | 3 --- kairos-server/src/config.rs | 2 +- kairos-server/src/l1_sync/event_manager.rs | 2 +- kairos-server/src/state.rs | 11 ++++++++++- .../src/state/transactions/batch_state.rs | 17 ++++++++++++----- 6 files changed, 25 insertions(+), 11 deletions(-) delete mode 100644 kairos-server/.env diff --git a/.env b/.env index d78b4036..0ef27809 100644 --- a/.env +++ b/.env @@ -8,3 +8,4 @@ KAIROS_SERVER_MAX_BATCH_SECONDS=60 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_CONTRACT_HASH="0000000000000000000000000000000000000000000000000000000000000000" diff --git a/kairos-server/.env b/kairos-server/.env deleted file mode 100644 index 9c37acae..00000000 --- a/kairos-server/.env +++ /dev/null @@ -1,3 +0,0 @@ -KAIROS_SERVER_SOCKET_ADDR="127.0.0.1:7893" -KAIROS_SERVER_CASPER_RPC="http://127.0.0.1:11101/rpc" -KAIROS_SERVER_CASPER_CONTRACT_HASH="0000000000000000000000000000000000000000000000000000000000000000" diff --git a/kairos-server/src/config.rs b/kairos-server/src/config.rs index b0952370..652d7f4b 100644 --- a/kairos-server/src/config.rs +++ b/kairos-server/src/config.rs @@ -16,7 +16,7 @@ impl ServerConfig { let socket_addr = parse_env_as::("KAIROS_SERVER_SOCKET_ADDR")?; let casper_rpc = parse_env_as::("KAIROS_SERVER_CASPER_RPC")?; let batch_config = BatchConfig::from_env()?; - let casper_contract_hash = parse_env_as::("KAIROS_SERVER_CASPER_CONTRACT_HASH")?; + let casper_contract_hash = parse_env_as::("KAIROS_CONTRACT_HASH")?; Ok(Self { socket_addr, diff --git a/kairos-server/src/l1_sync/event_manager.rs b/kairos-server/src/l1_sync/event_manager.rs index 1f71a16c..4e6c9b4c 100644 --- a/kairos-server/src/l1_sync/event_manager.rs +++ b/kairos-server/src/l1_sync/event_manager.rs @@ -57,7 +57,7 @@ impl EventManager { // parse them here with `kairos-tx` and then push to Data Availability layer. match event.name.as_str() { - "Deposit" => { + "L1Deposit" => { // Parse simplified deposit data. let (deposit, _) = L1Deposit::from_bytes(&event_bytes) .expect("Failed to parse deposit event from bytes"); diff --git a/kairos-server/src/state.rs b/kairos-server/src/state.rs index 1188687d..456c1245 100644 --- a/kairos-server/src/state.rs +++ b/kairos-server/src/state.rs @@ -44,7 +44,15 @@ impl BatchStateManager { let batch_output_handler = tokio::spawn(async move { while let Some(batch_output) = batch_rec.recv().await { - let prove_url = config.proving_server.join("prove").expect("Invalid URL"); + tracing::info!( + "Sending batch output to proving server: {:?}", + batch_output.proof_inputs.transactions + ); + + let prove_url = config + .proving_server + .join("/api/v1/prove/batch") + .expect("Invalid URL"); let res = reqwest::Client::new() .post(prove_url) @@ -58,6 +66,7 @@ impl BatchStateManager { if res.status().is_success() { // TODO send the proof to layer 1 + tracing::info!("Proving server returned success"); } else { tracing::error!("Proving server returned an error: {:?}", res); panic!("Proving server returned an error: {:?}", res); diff --git a/kairos-server/src/state/transactions/batch_state.rs b/kairos-server/src/state/transactions/batch_state.rs index 9c59ffc4..bd05308a 100644 --- a/kairos-server/src/state/transactions/batch_state.rs +++ b/kairos-server/src/state/transactions/batch_state.rs @@ -29,11 +29,14 @@ impl BatchState, Account>> { pub fn execute_transaction(&mut self, txn: KairosTransaction) -> Result<(), AppErr> { match txn { KairosTransaction::Transfer(ref transfer) => { + tracing::info!("Executing transfer: {:?}", transfer); self.account_trie .precheck_transfer(&transfer.public_key, &transfer.transaction, transfer.nonce) .map_err(|err| { - AppErr::new(anyhow!("transfer precheck caught: {err}")) - .set_status(StatusCode::CONFLICT) + AppErr::new(anyhow!( + "transfer precheck caught: {err}\n With transfer: {transfer:?}" + )) + .set_status(StatusCode::CONFLICT) })?; let _ = self @@ -42,20 +45,24 @@ impl BatchState, Account>> { .map_err(|err| panic!("transfer precheck failed to catch: {err}")); } KairosTransaction::Withdraw(ref withdraw) => { + tracing::info!("Executing withdraw: {:?}", withdraw); self.account_trie .precheck_withdraw(&withdraw.public_key, &withdraw.transaction, withdraw.nonce) .map_err(|err| { - AppErr::new(anyhow!("withdraw precheck caught: {err}")) - .set_status(StatusCode::CONFLICT) + AppErr::new(anyhow!( + "withdraw precheck caught: {err}\n With withdraw: {withdraw:?}" + )) + .set_status(StatusCode::CONFLICT) })?; let _ = self .account_trie .withdraw(&withdraw.public_key, &withdraw.transaction, withdraw.nonce) - .map_err(|err| panic!("withdraw precheck failed to catch: {err}")); + .map_err(|err| panic!("withdraw precheck failed to catch: {err}\n With withdraw: {withdraw:?}")); } KairosTransaction::Deposit(ref deposit) => { + tracing::info!("Executing deposit: {:?}", deposit); self.account_trie.precheck_deposit(deposit).map_err(|err| { AppErr::new(anyhow!("deposit precheck caught: {err}")) .set_status(StatusCode::CONFLICT) From b2b6c106e0bb77c4e57eb8774664b51be6220e00 Mon Sep 17 00:00:00 2001 From: Avi Dessauer Date: Tue, 25 Jun 2024 22:48:11 -0500 Subject: [PATCH 35/40] Use deposit public keys from l1 not mock --- kairos-server/src/l1_sync/event_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kairos-server/src/l1_sync/event_manager.rs b/kairos-server/src/l1_sync/event_manager.rs index 4e6c9b4c..5a3517d2 100644 --- a/kairos-server/src/l1_sync/event_manager.rs +++ b/kairos-server/src/l1_sync/event_manager.rs @@ -63,7 +63,7 @@ impl EventManager { .expect("Failed to parse deposit event from bytes"); let amount = deposit.amount; - let recipient: Vec = "cafebabe".into(); // CAUTION: Using mocked recipient, as event does NOT contain depositor's public key. + let recipient: Vec = deposit.recipient; let txn = KairosTransaction::Deposit(L1Deposit { amount, recipient }); // Push deposit to trie. From 061426f179b4817856c2a18559140fee7126e6e8 Mon Sep 17 00:00:00 2001 From: Avi Dessauer Date: Wed, 26 Jun 2024 01:02:23 -0500 Subject: [PATCH 36/40] Submit the batch to l1 --- kairos-server/Cargo.toml | 1 + kairos-server/src/config.rs | 19 ++++++++++ kairos-server/src/lib.rs | 2 +- kairos-server/src/state.rs | 50 +++++++++++++++++++++---- kairos-server/src/state/submit_batch.rs | 40 ++++++++++++++++++++ kairos-server/tests/transactions.rs | 3 +- kairos-test-utils/src/kairos.rs | 1 + 7 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 kairos-server/src/state/submit_batch.rs diff --git a/kairos-server/Cargo.toml b/kairos-server/Cargo.toml index 560cd066..e433ebb2 100644 --- a/kairos-server/Cargo.toml +++ b/kairos-server/Cargo.toml @@ -27,6 +27,7 @@ axum-extra = { version = "0.9", features = [ ] } anyhow = "1" casper-client.workspace = true +casper-client-types = { workspace = true, features = ["std"] } # TODO: Change `std` -> `std-fs-io` in the future version. rand = "0.8" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/kairos-server/src/config.rs b/kairos-server/src/config.rs index 652d7f4b..036444d5 100644 --- a/kairos-server/src/config.rs +++ b/kairos-server/src/config.rs @@ -1,10 +1,15 @@ +use casper_client_types::SecretKey; use reqwest::Url; use std::net::SocketAddr; +use std::path::PathBuf; use std::time::Duration; use std::{fmt, str::FromStr}; #[derive(Clone, Debug)] pub struct ServerConfig { + /// Set by the environment variable `KAIROS_SERVER_SECRET_KEY_FILE`. + /// This is checked at startup to ensure SecretKey::from_file is successful. + pub secret_key_file: Option, pub socket_addr: SocketAddr, pub casper_rpc: Url, pub casper_contract_hash: String, @@ -17,8 +22,22 @@ impl ServerConfig { let casper_rpc = parse_env_as::("KAIROS_SERVER_CASPER_RPC")?; let batch_config = BatchConfig::from_env()?; let casper_contract_hash = parse_env_as::("KAIROS_CONTRACT_HASH")?; + let secret_key_file = + parse_env_as_opt::("KAIROS_SERVER_SECRET_KEY_FILE")?.map(PathBuf::from); + + match &secret_key_file { + Some(secret_key_file) => { + if SecretKey::from_file(secret_key_file).is_err() { + return Err("Invalid secret key".to_string()); + } + } + None => { + tracing::warn!("No secret key file provided. This server will not be able to sign or send depolys."); + } + } Ok(Self { + secret_key_file, socket_addr, casper_rpc, casper_contract_hash, diff --git a/kairos-server/src/lib.rs b/kairos-server/src/lib.rs index 44ce19f4..ca0412c9 100644 --- a/kairos-server/src/lib.rs +++ b/kairos-server/src/lib.rs @@ -69,7 +69,7 @@ pub async fn run(config: ServerConfig) { tracing::info!("listening on `{}`", listener.local_addr().unwrap()); let state = Arc::new(ServerStateInner { - batch_state_manager: BatchStateManager::new_empty(config.batch_config.clone()), + batch_state_manager: BatchStateManager::new_empty(&config), server_config: config.clone(), }); diff --git a/kairos-server/src/state.rs b/kairos-server/src/state.rs index 456c1245..50cd0d73 100644 --- a/kairos-server/src/state.rs +++ b/kairos-server/src/state.rs @@ -1,12 +1,15 @@ +pub mod submit_batch; pub mod transactions; mod trie; use std::{sync::Arc, thread}; +use casper_client_types::ContractHash; +use hex::FromHex; use tokio::{sync::mpsc, task}; pub use self::trie::TrieStateThreadMsg; -use crate::config::{BatchConfig, ServerConfig}; +use crate::{config::ServerConfig, state::submit_batch::submit_proof_to_contract}; use kairos_circuit_logic::transactions::KairosTransaction; use kairos_trie::{stored::memory_db::MemoryDb, NodeHash, TrieRoot}; @@ -35,12 +38,30 @@ impl BatchStateManager { /// Create a new `BatchStateManager` with the given `db` and `batch_root`. /// `batch_root` and it's descendants must be in the `db`. /// This method spawns the trie state thread, it should be called only once. - pub fn new(config: BatchConfig, db: trie::Database, batch_root: TrieRoot) -> Self { + pub fn new(config: &ServerConfig, db: trie::Database, batch_root: TrieRoot) -> Self { + let batch_config = config.batch_config.clone(); + let casper_rpc = config.casper_rpc.clone(); + let contract_hash_bytes = <[u8; 32]>::from_hex(config.casper_contract_hash.as_str()) + .expect("Invalid contract hash"); + let contract_hash = ContractHash::new(contract_hash_bytes); + + let secret_key = config + .secret_key_file + .as_ref() + // We already checked that we can read the secret key in at startup. + // SecretKey does not implement Clone, so we need to clone the path and read it again. + .map(|f| casper_client_types::SecretKey::from_file(f).expect("Invalid secret key")); + let (queued_transactions, txn_receiver) = mpsc::channel(1000); // This queue provides back pressure to the trie thread. let (batch_sender, mut batch_rec) = mpsc::channel(10); - let trie_thread = - trie::spawn_state_thread(config.clone(), txn_receiver, batch_sender, db, batch_root); + let trie_thread = trie::spawn_state_thread( + config.batch_config.clone(), + txn_receiver, + batch_sender, + db, + batch_root, + ); let batch_output_handler = tokio::spawn(async move { while let Some(batch_output) = batch_rec.recv().await { @@ -49,7 +70,7 @@ impl BatchStateManager { batch_output.proof_inputs.transactions ); - let prove_url = config + let prove_url = batch_config .proving_server .join("/api/v1/prove/batch") .expect("Invalid URL"); @@ -65,8 +86,23 @@ impl BatchStateManager { }); if res.status().is_success() { - // TODO send the proof to layer 1 tracing::info!("Proving server returned success"); + let proof_serialized = res.bytes().await.unwrap_or_else(|e| { + tracing::error!("Could not read response from proving server: {}", e); + panic!("Could not read response from proving server: {}", e); + }); + + if let Some(secret_key) = secret_key.as_ref() { + submit_proof_to_contract( + secret_key, + contract_hash, + casper_rpc.clone(), + proof_serialized.to_vec(), + ) + .await + } else { + tracing::warn!("No secret key provided. Not submitting proof to contract."); + } } else { tracing::error!("Proving server returned an error: {:?}", res); panic!("Proving server returned an error: {:?}", res); @@ -83,7 +119,7 @@ impl BatchStateManager { /// Create a new `BatchStateManager` with an empty `MemoryDb` and an empty `TrieRoot`. /// This is useful for testing. - pub fn new_empty(config: BatchConfig) -> Self { + pub fn new_empty(config: &ServerConfig) -> Self { Self::new(config, MemoryDb::empty(), TrieRoot::default()) } diff --git a/kairos-server/src/state/submit_batch.rs b/kairos-server/src/state/submit_batch.rs new file mode 100644 index 00000000..1676cec6 --- /dev/null +++ b/kairos-server/src/state/submit_batch.rs @@ -0,0 +1,40 @@ +use casper_client::types::{DeployBuilder, ExecutableDeployItem, TimeDiff, Timestamp}; +use casper_client_types::{bytesrepr::Bytes, runtime_args, ContractHash, RuntimeArgs, SecretKey}; +use rand::random; +use reqwest::Url; + +pub const MAX_GAS_FEE_PAYMENT_AMOUNT: u64 = 10_000_000_000_000; + +// TODO: retry request on failure, improve error handling +pub async fn submit_proof_to_contract( + signer: &SecretKey, + contract_hash: ContractHash, + casper_rpc: Url, + proof_serialized: Vec, +) { + let submit_batch = ExecutableDeployItem::StoredContractByHash { + hash: contract_hash, + entry_point: "submit_batch".into(), + args: runtime_args! { + "risc0_receipt" => Bytes::from(proof_serialized), + }, + }; + + let deploy = DeployBuilder::new(env!("CASPER_CHAIN_NAME"), submit_batch, signer) + .with_standard_payment(MAX_GAS_FEE_PAYMENT_AMOUNT) + .with_timestamp(Timestamp::now()) + .with_ttl(TimeDiff::from_millis(60_000)) + .build() + .expect("could not build deploy"); + + let r = casper_client::put_deploy( + casper_client::JsonRpcId::Number(random()), + casper_rpc.as_str(), + casper_client::Verbosity::High, + deploy, + ) + .await + .expect("could not put deploy"); + + tracing::info!("Deploy successful: {:?}", r); +} diff --git a/kairos-server/tests/transactions.rs b/kairos-server/tests/transactions.rs index c3ac17ed..9d4487f9 100644 --- a/kairos-server/tests/transactions.rs +++ b/kairos-server/tests/transactions.rs @@ -45,6 +45,7 @@ fn new_test_app_with_casper_node(casper_node_url: &Url) -> TestServer { }); let config = TestServerConfig::builder().mock_transport().build(); let server_config = ServerConfig { + secret_key_file: None, socket_addr: "0.0.0.0:0".parse().unwrap(), casper_rpc: casper_node_url.clone(), casper_contract_hash: "0000000000000000000000000000000000000000000000000000000000000000" @@ -58,7 +59,7 @@ fn new_test_app_with_casper_node(casper_node_url: &Url) -> TestServer { }; let state = Arc::new(ServerStateInner { - batch_state_manager: BatchStateManager::new_empty(server_config.batch_config.clone()), + batch_state_manager: BatchStateManager::new_empty(&server_config), server_config, }); diff --git a/kairos-test-utils/src/kairos.rs b/kairos-test-utils/src/kairos.rs index ce9e6d0f..685d293f 100644 --- a/kairos-test-utils/src/kairos.rs +++ b/kairos-test-utils/src/kairos.rs @@ -40,6 +40,7 @@ impl Kairos { }); let config = ServerConfig { + secret_key_file: None, socket_addr, casper_rpc, casper_contract_hash: String::from( From 186581169c127369876b57478d72a04f032708b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Wed, 26 Jun 2024 09:13:21 +0200 Subject: [PATCH 37/40] tests/e2e: wait for successful deposit deploy execution --- nixos/tests/end-to-end.nix | 217 +++++++++++++++++++++++-------------- 1 file changed, 134 insertions(+), 83 deletions(-) diff --git a/nixos/tests/end-to-end.nix b/nixos/tests/end-to-end.nix index 33165337..102ac188 100644 --- a/nixos/tests/end-to-end.nix +++ b/nixos/tests/end-to-end.nix @@ -4,12 +4,23 @@ , testResources ? ../../kairos-cli/tests/fixtures , kairos-contracts , cctlModule -, casper-client-rs , fetchurl +, casper-client-rs +, writeShellScript +, jq }: +let + # This is where wget (see test) will place the files advertised at http://kairos/cctl/users if the cctl module is enabled + clientUsersDirectory = "kairos/cctl/users"; + cctlPort = 11101; + casperNodeAddress = "http://localhost:${builtins.toString cctlPort}"; + cctlWorkingDirectory = "/var/lib/cctl"; + contractHashName = "kairos_contract_package_hash"; + # The path where cctl will write the deployed contract hash on the servers filesystem + serverContractHashPath = "${cctlWorkingDirectory}/contracts/${contractHashName}"; +in nixosTest { name = "kairos e2e test"; - nodes = { server = { config, lib, ... }: { imports = [ @@ -17,17 +28,22 @@ nixosTest { cctlModule ]; - # modify acme for nixos-test environment - security.acme = { - preliminarySelfsigned = true; - defaults.server = "https://example.com"; # don't spam the acme production server + virtualisation.cores = 4; + virtualisation.memorySize = 4096; + + # allow HTTP for nixos-test environment + services.nginx.virtualHosts.${config.networking.hostName} = { + forceSSL = lib.mkForce false; + enableACME = lib.mkForce false; }; + environment.systemPackages = [ casper-client-rs ]; - # allow HTTP for nixos-test environment - services.nginx.virtualHosts.${config.networking.hostName}.forceSSL = lib.mkForce false; services.cctl = { enable = true; + port = cctlPort; + workingDirectory = cctlWorkingDirectory; + contract = { "${contractHashName}" = kairos-contracts + "/bin/demo-contract-optimized.wasm"; }; chainspec = fetchurl { url = "https://raw.githubusercontent.com/cspr-rad/casper-node/53136ac5f004f2ae70a75b4eeb2ff7d907aff6aa/resources/local/chainspec.toml.in"; hash = "sha256-b/6c5o3JXFlaTgTHxs8JepaHzjMG75knzlKKqRd/7pc="; @@ -37,7 +53,22 @@ nixosTest { hash = "sha256-ZuNbxw0nBjuONEZRK8Ru96zZQak4MEQ/eM1fA6esyCM="; }; }; - services.kairos.casperRpcUrl = "http://localhost:${builtins.toString config.services.cctl.port}/rpc"; + + services.kairos = { + casperRpcUrl = "http://localhost:${builtins.toString config.services.cctl.port}/rpc"; + demoContractHash = "0000000000000000000000000000000000000000000000000000000000000000"; + }; + + # We have to wait for cctl to deploy the contract to be able to obtain and export the contract hash + systemd.services.kairos = { + path = [ casper-client-rs jq ]; + after = [ "network-online.target" "cctl.service" ]; + requires = [ "network-online.target" "cctl.service" ]; + serviceConfig.ExecStart = lib.mkForce (writeShellScript "start-kairos" '' + export KAIROS_SERVER_DEMO_CONTRACT_HASH=$(cat ${serverContractHashPath}) + ${lib.getExe kairos} + ''); + }; }; client = { pkgs, ... }: { @@ -45,79 +76,99 @@ nixosTest { }; }; - testScript = { nodes, ... }: - let - casperNodeAddress = "http://localhost:${builtins.toString nodes.server.services.cctl.port}"; - serverUsersDirectory = nodes.server.services.cctl.workingDirectory + "/assets/users"; - # This is where wget will place the files from http://kairos/cctl/users - clientUsersDirectory = "kairos/cctl/users"; - in - '' - # import json - - start_all() - - kairos.wait_for_unit("cctl.service") - - kairos.wait_for_unit("kairos.service") - kairos.wait_for_unit("nginx.service") - kairos.wait_for_open_port(80) - - client.wait_for_unit ("multi-user.target") - - # We need to copy the generated assets from the server to our client - # For more details, see cctl module implementation - client.succeed("wget --no-parent -r http://kairos/cctl/users/") - - kairos.succeed("casper-client get-node-status --node-address ${casperNodeAddress}") - - # Deploy the demo contract - # chain-name see: https://github.com/casper-network/cctl/blob/745155d080934c409d98266f912b8fd2b7e28a00/utils/constants.sh#L66 - kairos.succeed("casper-client put-deploy --node-address ${casperNodeAddress} --chain-name cspr-dev-cctl --secret-key ${serverUsersDirectory}/user-1/secret_key.pem --payment-amount 5000000000000 --session-path ${kairos-contracts}/bin/demo-contract-optimized.wasm") - - # CLI with ed25519 - cli_output = client.succeed("kairos-cli --kairos-server-address http://kairos deposit --amount 1000 --private-key ${clientUsersDirectory}/user-1/secret_key.pem") - assert int(cli_output, 16), "The deposit command did not output a hex encoded deploy hash. The output was {}".format(cli_output) - - # TODO Transfer and withdraw can only work once deposit deploys are processed and the users actually have an account - # REST API - # Tx Payload - # nonce = 0 - # transfer: - # recipient = deadbabe - # amount = 1000 - # - # transfer_payload = "300f020100a10a0404deadbabe020203e8" - # transfer_request = { "public_key": "cafebabe", "payload": transfer_payload, "signature": "deadbeef" } - # client.succeed("curl --fail-with-body -X POST http://kairos/api/v1/transfer -H 'Content-Type: application/json' -d '{}'".format(json.dumps(transfer_request))) - - # Tx Payload - # nonce = 0 - # withdrawal: - # amount = 1000 - # - # withdraw_payload = "3009020100a204020203e8" - # withdraw_request = { "public_key": "deadbabe", "payload": withdraw_payload, "signature": "deadbeef" } - # client.succeed("curl --fail-with-body -X POST http://kairos/api/v1/withdraw -H 'Content-Type: application/json' -d '{}'".format(json.dumps(withdraw_request))) - - # TODO Transfer and withdraw can only work once deposit deploys are processed and the users actually have an account - # CLI with ed25519 - # cli_output = client.succeed("kairos-cli transfer --recipient '01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356' --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") - # assert "ok\n" in cli_output - - # cli_output = client.succeed("kairos-cli withdraw --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") - # assert "ok\n" in cli_output - - # TODO cctl does not provide any secp256k1 keys - # CLI with secp256k1 - # cli_output = client.succeed("kairos-cli --kairos-server-address http://kairos deposit --amount 1000 --private-key ${testResources}/secp256k1/secret_key.pem") - # assert "ok\n" in cli_output - - # cli_output = client.succeed("kairos-cli transfer --recipient '01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356' --amount 1000 --private-key ${testResources}/secp256k1/secret_key.pem") - # assert "ok\n" in cli_output - - # cli_output = client.succeed("kairos-cli withdraw --amount 1000 --private-key ${testResources}/secp256k1/secret_key.pem") - # assert "ok\n" in cli_output - ''; + extraPythonPackages = p: [ p.backoff ]; + testScript = '' + import json + import backoff + + # Utils + def verify_deploy_success(json_data): + # Check if the "Success" key is present + try: + if "result" in json_data and "execution_results" in json_data["result"]: + for execution_result in json_data["result"]["execution_results"]: + if "result" in execution_result and "Success" in execution_result["result"]: + return True + except KeyError: + pass + return False + + @backoff.on_exception(backoff.expo, Exception, max_tries=5, jitter=backoff.full_jitter) + def wait_for_successful_deploy(deploy_hash): + client_output = kairos.succeed("casper-client get-deploy --node-address ${casperNodeAddress} {}".format(deploy_hash)) + get_deploy_result = json.loads(client_output) + if not verify_deploy_success(get_deploy_result): + raise Exception("Success key not found in JSON") + + # Test + start_all() + + kairos.wait_for_unit("cctl.service") + + kairos.wait_for_unit("kairos.service") + kairos.wait_for_unit("nginx.service") + kairos.wait_for_open_port(80) + + client.wait_for_unit ("multi-user.target") + + # We need to copy the generated assets from the server to our client, because we use filepaths + # in our cli, therefore we need to make sure that the files generated by cctl on the server + # are also available on the client + # 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 + recipient = client.succeed("cat ${clientUsersDirectory}/user-2/public_key_hex") + 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(recipient, private_key, contract_hash)) + 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) + + # TODO Transfer and withdraw can only work once deposit deploys are processed and the users actually have an account + # REST API + # Tx Payload + # nonce = 0 + # transfer: + # recipient = deadbabe + # amount = 1000 + # + # transfer_payload = "300f020100a10a0404deadbabe020203e8" + # transfer_request = { "public_key": "cafebabe", "payload": transfer_payload, "signature": "deadbeef" } + # client.succeed("curl --fail-with-body -X POST http://kairos/api/v1/transfer -H 'Content-Type: application/json' -d '{}'".format(json.dumps(transfer_request))) + + # Tx Payload + # nonce = 0 + # withdrawal: + # amount = 1000 + # + # withdraw_payload = "3009020100a204020203e8" + # withdraw_request = { "public_key": "deadbabe", "payload": withdraw_payload, "signature": "deadbeef" } + # client.succeed("curl --fail-with-body -X POST http://kairos/api/v1/withdraw -H 'Content-Type: application/json' -d '{}'".format(json.dumps(withdraw_request))) + + # TODO Transfer and withdraw can only work once deposit deploys are processed and the users actually have an account + # CLI with ed25519 + # cli_output = client.succeed("kairos-cli transfer --recipient '01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356' --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") + # assert "ok\n" in cli_output + + # cli_output = client.succeed("kairos-cli withdraw --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") + # assert "ok\n" in cli_output + + # TODO cctl does not provide any secp256k1 keys + # CLI with secp256k1 + # cli_output = client.succeed("kairos-cli --kairos-server-address http://kairos deposit --amount 1000 --private-key ${testResources}/secp256k1/secret_key.pem") + # assert "ok\n" in cli_output + + # cli_output = client.succeed("kairos-cli transfer --recipient '01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356' --amount 1000 --private-key ${testResources}/secp256k1/secret_key.pem") + # assert "ok\n" in cli_output + + # cli_output = client.succeed("kairos-cli withdraw --amount 1000 --private-key ${testResources}/secp256k1/secret_key.pem") + # assert "ok\n" in cli_output + ''; } From cd125307c365419cbad1c977fbeddae8bd7f3af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Wed, 26 Jun 2024 09:29:18 +0200 Subject: [PATCH 38/40] tests/e2e: test transfer --- nixos/tests/end-to-end.nix | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/nixos/tests/end-to-end.nix b/nixos/tests/end-to-end.nix index 102ac188..c31947d3 100644 --- a/nixos/tests/end-to-end.nix +++ b/nixos/tests/end-to-end.nix @@ -123,9 +123,9 @@ nixosTest { # CLI with ed25519 # deposit - recipient = client.succeed("cat ${clientUsersDirectory}/user-2/public_key_hex") - 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(recipient, private_key, contract_hash)) + 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)) 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) @@ -155,6 +155,10 @@ nixosTest { # CLI with ed25519 # cli_output = client.succeed("kairos-cli transfer --recipient '01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356' --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") # assert "ok\n" in cli_output + # 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 # cli_output = client.succeed("kairos-cli withdraw --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") # assert "ok\n" in cli_output From e78bde607ae88ba4948ce0fe3925f6ca54880618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Wed, 26 Jun 2024 09:29:34 +0200 Subject: [PATCH 39/40] tests/e2e: update todos --- nixos/tests/end-to-end.nix | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/nixos/tests/end-to-end.nix b/nixos/tests/end-to-end.nix index c31947d3..e13c4983 100644 --- a/nixos/tests/end-to-end.nix +++ b/nixos/tests/end-to-end.nix @@ -130,38 +130,12 @@ nixosTest { wait_for_successful_deploy(deposit_deploy_hash) - # TODO Transfer and withdraw can only work once deposit deploys are processed and the users actually have an account - # REST API - # Tx Payload - # nonce = 0 - # transfer: - # recipient = deadbabe - # amount = 1000 - # - # transfer_payload = "300f020100a10a0404deadbabe020203e8" - # transfer_request = { "public_key": "cafebabe", "payload": transfer_payload, "signature": "deadbeef" } - # client.succeed("curl --fail-with-body -X POST http://kairos/api/v1/transfer -H 'Content-Type: application/json' -d '{}'".format(json.dumps(transfer_request))) - - # Tx Payload - # nonce = 0 - # withdrawal: - # amount = 1000 - # - # withdraw_payload = "3009020100a204020203e8" - # withdraw_request = { "public_key": "deadbabe", "payload": withdraw_payload, "signature": "deadbeef" } - # client.succeed("curl --fail-with-body -X POST http://kairos/api/v1/withdraw -H 'Content-Type: application/json' -d '{}'".format(json.dumps(withdraw_request))) - - # TODO Transfer and withdraw can only work once deposit deploys are processed and the users actually have an account - # CLI with ed25519 - # cli_output = client.succeed("kairos-cli transfer --recipient '01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356' --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") - # assert "ok\n" in cli_output # 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 - # cli_output = client.succeed("kairos-cli withdraw --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") - # assert "ok\n" in cli_output + # TODO test withdraw # TODO cctl does not provide any secp256k1 keys # CLI with secp256k1 From f3add1d439564d4668d246c352446feae0872acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Wed, 26 Jun 2024 09:33:11 +0200 Subject: [PATCH 40/40] nixos/cctl: server the contracts directory --- nixos/modules/cctl.nix | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nixos/modules/cctl.nix b/nixos/modules/cctl.nix index 6f729a44..bef9edec 100644 --- a/nixos/modules/cctl.nix +++ b/nixos/modules/cctl.nix @@ -145,6 +145,13 @@ in add_header Content-Type 'text/plain charset=UTF-8'; ''; }; + "/cctl/contracts/" = { + alias = "${cfg.workingDirectory}/contracts/"; + extraConfig = '' + autoindex on; + add_header Content-Type 'text/plain charset=UTF-8'; + ''; + }; }; }; };