From cf9c33fcc5db2ae625e21880463ce31b67223a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Tue, 23 Jul 2024 10:27:39 +0200 Subject: [PATCH 1/9] kairos-data: make amounts signed for transaction filter --- kairos-data/src/transaction.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kairos-data/src/transaction.rs b/kairos-data/src/transaction.rs index 362462d1..111269c6 100644 --- a/kairos-data/src/transaction.rs +++ b/kairos-data/src/transaction.rs @@ -35,8 +35,8 @@ pub struct TransactionFilter { pub sender: Option, pub min_timestamp: Option, pub max_timestamp: Option, - pub min_amount: Option, - pub max_amount: Option, + pub min_amount: Option, + pub max_amount: Option, pub recipient: Option, } From 08a23cde4deff9c1eaeb8a723666ad7ba6887648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Tue, 23 Jul 2024 10:28:08 +0200 Subject: [PATCH 2/9] kairos-data: add missing serde serialize/deserialize traits --- kairos-data/src/transaction.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kairos-data/src/transaction.rs b/kairos-data/src/transaction.rs index 111269c6..006a4514 100644 --- a/kairos-data/src/transaction.rs +++ b/kairos-data/src/transaction.rs @@ -17,7 +17,7 @@ pub enum Transaction { Withdrawal, } -#[derive(Queryable, Debug, Identifiable, Insertable, Serialize, Selectable)] +#[derive(Queryable, Debug, Identifiable, Insertable, Serialize, Selectable, Deserialize)] #[diesel(primary_key(timestamp, amount, public_key))] #[diesel(table_name = transactions)] #[diesel(check_for_backend(diesel::pg::Pg))] @@ -30,7 +30,7 @@ pub struct Transactions { pub recipient: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug, Serialize)] pub struct TransactionFilter { pub sender: Option, pub min_timestamp: Option, From 87b0dc8ca10f3137bd0bdc829e354c01bcd23db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Tue, 23 Jul 2024 10:28:22 +0200 Subject: [PATCH 3/9] kairos-server/fetch: simplify --- kairos-server/src/routes/fetch.rs | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/kairos-server/src/routes/fetch.rs b/kairos-server/src/routes/fetch.rs index 6a9a1c7a..92d4862f 100644 --- a/kairos-server/src/routes/fetch.rs +++ b/kairos-server/src/routes/fetch.rs @@ -1,22 +1,10 @@ use axum::{extract::State, Json}; use axum_extra::routing::TypedPath; -use chrono::NaiveDateTime; use kairos_data::transaction::*; -use serde::Deserialize; use tracing::instrument; use crate::{state::ServerState, AppErr}; -#[derive(Deserialize, Debug)] -pub struct QueryTransactionsPayload { - sender: Option, - min_timestamp: Option, - max_timestamp: Option, - min_amount: Option, - max_amount: Option, - recipient: Option, -} - #[derive(TypedPath)] #[typed_path("/api/v1/transactions")] pub struct QueryTransactionsPath; @@ -25,22 +13,7 @@ pub struct QueryTransactionsPath; pub async fn query_transactions_handler( _: QueryTransactionsPath, State(state): State, - Json(payload): Json, + Json(filter): Json, ) -> Result>, AppErr> { - let filter = TransactionFilter { - sender: payload.sender, - min_timestamp: payload - .min_timestamp - .and_then(|ts| NaiveDateTime::parse_from_str(&ts, "%Y-%m-%d %H:%M:%S").ok()), - max_timestamp: payload - .max_timestamp - .and_then(|ts| NaiveDateTime::parse_from_str(&ts, "%Y-%m-%d %H:%M:%S").ok()), - min_amount: payload.min_amount, - max_amount: payload.max_amount, - recipient: payload.recipient, - }; - - let transactions = get(&state.pool, filter).await?; - - Ok(Json(transactions)) + get(&state.pool, filter).await.map(Json).map_err(Into::into) } From c595f0d68dd100b5bbf82a8d3215f41bb359da60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Tue, 23 Jul 2024 10:28:47 +0200 Subject: [PATCH 4/9] kairos-cli: implement fetch command --- Cargo.lock | 2 ++ kairos-cli/Cargo.toml | 4 ++- kairos-cli/src/client.rs | 26 +++++++++++++++++ kairos-cli/src/commands/fetch.rs | 48 ++++++++++++++++++++++++++++++++ kairos-cli/src/commands/mod.rs | 2 ++ kairos-cli/src/error.rs | 6 ++++ kairos-cli/src/lib.rs | 4 +++ 7 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 kairos-cli/src/commands/fetch.rs diff --git a/Cargo.lock b/Cargo.lock index 758f6dfa..bb827d8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2711,10 +2711,12 @@ dependencies = [ "casper-client", "casper-hashing 2.0.0", "casper-types 3.0.0", + "chrono", "clap", "dotenvy", "hex", "kairos-crypto", + "kairos-data", "kairos-server", "kairos-test-utils", "kairos-tx", diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index 6c2021b8..bda0f4b2 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -18,7 +18,7 @@ default = ["demo", "database"] all-tests = ["cctl-tests", "database"] cctl-tests = [] demo = ["dep:kairos-test-utils", "dep:tokio", "dep:dotenvy"] -database = ["kairos-server/database", "kairos-test-utils/database"] +database = ["dep:kairos-data", "dep:chrono", "kairos-server/database", "kairos-test-utils/database"] [dependencies] dotenvy = { version = "0.15", optional = true } @@ -27,9 +27,11 @@ 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"] } +chrono = { version = "0.4", optional = true } hex = "0.4" thiserror = "1" kairos-crypto = { path = "../kairos-crypto", features = [ "std", "fs" ] } +kairos-data = { path = "../kairos-data", optional = true } kairos-tx = { path = "../kairos-tx" } kairos-server = { path = "../kairos-server" } axum-extra = { version = "0.9", features = [ "typed-routing" ] } diff --git a/kairos-cli/src/client.rs b/kairos-cli/src/client.rs index 3c0d5f3c..11952f79 100644 --- a/kairos-cli/src/client.rs +++ b/kairos-cli/src/client.rs @@ -11,6 +11,11 @@ use std::fmt; use std::fs; use std::path::Path; +#[cfg(feature = "database")] +use kairos_data::transaction::{TransactionFilter, Transactions}; +#[cfg(feature = "database")] +use kairos_server::routes::fetch::QueryTransactionsPath; + // max amount allowed to be used on gas fees pub const MAX_GAS_FEE_PAYMENT_AMOUNT: u64 = 1_000_000_000_000; @@ -131,3 +136,24 @@ pub fn contract_hash(base_url: &Url) -> Result .map_err(KairosClientError::from) } } + +#[cfg(feature = "database")] +pub fn fetch( + base_url: &Url, + transaction_filter: &TransactionFilter, +) -> Result, KairosClientError> { + let response = reqwest::blocking::Client::new() + .post(base_url.join(QueryTransactionsPath::PATH).unwrap()) + .header("Content-Type", "application/json") + .json(&transaction_filter) + .send() + .map_err(KairosClientError::from)? + .error_for_status(); + + match response { + Err(err) => Err(KairosClientError::from(err)), + Ok(response) => response + .json::>() + .map_err(KairosClientError::from), + } +} diff --git a/kairos-cli/src/commands/fetch.rs b/kairos-cli/src/commands/fetch.rs new file mode 100644 index 00000000..0d221864 --- /dev/null +++ b/kairos-cli/src/commands/fetch.rs @@ -0,0 +1,48 @@ +use clap::Parser; +use reqwest::Url; + +use crate::client; +use crate::error::CliError; +use kairos_data::transaction::TransactionFilter; + +use chrono::NaiveDateTime; + +#[derive(Parser, Debug)] +pub struct Args { + #[arg(long, short, value_name = "PUBLIC_KEY_HEX")] + sender: Option, + #[arg(long, short, value_name = "ISO8601_TIMESTAMP")] + min_timestamp: Option, + #[arg(long, short, value_name = "ISO8601_TIMESTAMP")] + max_timestamp: Option, + #[arg(long, short, value_name = "NUM_MOTES")] + min_amount: Option, + #[arg(long, short, value_name = "NUM_MOTES")] + max_amount: Option, + #[arg(long, short, value_name = "PUBLIC_KEY_HEX")] + recipient: Option, +} + +pub fn run( + Args { + sender, + min_timestamp, + max_timestamp, + min_amount, + max_amount, + recipient, + }: Args, + kairos_server_address: Url, +) -> Result { + let transaction_filter = TransactionFilter { + sender, + min_timestamp, + max_timestamp, + min_amount, + max_amount, + recipient, + }; + let transactions = client::fetch(&kairos_server_address, &transaction_filter) + .map_err(Into::::into)?; + serde_json::to_string_pretty(&transactions).map_err(Into::into) +} diff --git a/kairos-cli/src/commands/mod.rs b/kairos-cli/src/commands/mod.rs index e917a1f7..3cde9465 100644 --- a/kairos-cli/src/commands/mod.rs +++ b/kairos-cli/src/commands/mod.rs @@ -1,4 +1,6 @@ pub mod deposit; +#[cfg(feature = "database")] +pub mod fetch; #[cfg(feature = "demo")] pub mod run_cctl; pub mod transfer; diff --git a/kairos-cli/src/error.rs b/kairos-cli/src/error.rs index 83a253cb..3d407ad4 100644 --- a/kairos-cli/src/error.rs +++ b/kairos-cli/src/error.rs @@ -6,6 +6,12 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum CliError { + /// Failed to serialize to JSON string. + #[error("failed to parse hex string: {error}")] + SerializationError { + #[from] + error: serde_json::Error, + }, /// Cryptography error. #[error("cryptography error: {error}")] CryptoError { diff --git a/kairos-cli/src/lib.rs b/kairos-cli/src/lib.rs index 01d400e9..fcf050e7 100644 --- a/kairos-cli/src/lib.rs +++ b/kairos-cli/src/lib.rs @@ -27,6 +27,8 @@ pub enum Command { Transfer(commands::transfer::Args), #[command(about = "Withdraws funds from your account")] Withdraw(commands::withdraw::Args), + #[cfg(feature = "database")] + Fetch(commands::fetch::Args), #[cfg(feature = "demo")] RunDemoCctl, @@ -42,6 +44,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 = "database")] + Command::Fetch(args) => commands::fetch::run(args, kairos_server_address), #[cfg(feature = "demo")] Command::RunDemoCctl => commands::run_cctl::run(), From b4152776d49aa73bb0df39b6c3ae09088980807c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Tue, 23 Jul 2024 10:28:59 +0200 Subject: [PATCH 5/9] tests/e2e: test fetch command --- nixos/tests/end-to-end.nix | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nixos/tests/end-to-end.nix b/nixos/tests/end-to-end.nix index d506cc0f..29aab5e7 100644 --- a/nixos/tests/end-to-end.nix +++ b/nixos/tests/end-to-end.nix @@ -105,8 +105,7 @@ nixosTest { @backoff.on_exception(backoff.expo, Exception, max_tries=5, jitter=backoff.full_jitter) def wait_for_deposit(depositor, amount): - transactions_query = { "sender": depositor } - transactions_result = client.succeed("curl --fail-with-body -X POST http://kairos/api/v1/transactions -H 'Content-Type: application/json' -d '{}'".format(json.dumps(transactions_query))) + transactions_result = client.succeed("kairos-cli --kairos-server-address http://kairos fetch --sender {}".format(depositor)) transactions = json.loads(transactions_result) if not any(transaction.get("public_key") == depositor and transaction.get("amount") == str(amount) for transaction in transactions): raise Exception("Couldn't find deposit for depositor {} with amount {} in transactions\n:{}".format(depositor, amount, transactions)) @@ -147,8 +146,7 @@ nixosTest { assert "Transfer successfully sent to L2\n" in transfer_output, "The transfer command was not successful: {}".format(transfer_output) # data availability - transactions_query = { "recipient": beneficiary } - transactions_result = client.succeed("curl --fail-with-body -X POST http://kairos/api/v1/transactions -H 'Content-Type: application/json' -d '{}'".format(json.dumps(transactions_query))) + transactions_result = client.succeed("kairos-cli --kairos-server-address http://kairos fetch --recipient {}".format(beneficiary)) transactions = json.loads(transactions_result) assert any(transaction.get("recipient") == beneficiary and transaction.get("amount") == str(1000) for transaction in transactions), "Couldn't find the transfer in the L2's DA: {}".format(transactions) From 8c87bf90878314b990e992bc151438f06a520687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Wed, 24 Jul 2024 10:00:24 +0200 Subject: [PATCH 6/9] kairos-data: add transaction type filter --- kairos-cli/src/commands/fetch.rs | 25 +++++++++++++++++++++++-- kairos-data/src/transaction.rs | 4 ++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/kairos-cli/src/commands/fetch.rs b/kairos-cli/src/commands/fetch.rs index 0d221864..9d146c48 100644 --- a/kairos-cli/src/commands/fetch.rs +++ b/kairos-cli/src/commands/fetch.rs @@ -1,9 +1,9 @@ -use clap::Parser; +use clap::{Parser, ValueEnum}; use reqwest::Url; use crate::client; use crate::error::CliError; -use kairos_data::transaction::TransactionFilter; +use kairos_data::transaction::{Transaction, TransactionFilter}; use chrono::NaiveDateTime; @@ -21,6 +21,25 @@ pub struct Args { max_amount: Option, #[arg(long, short, value_name = "PUBLIC_KEY_HEX")] recipient: Option, + #[arg(long, short, value_name = "TRANSACTION_TYPE", value_enum)] + transaction_type: Option, +} + +#[derive(ValueEnum, Debug, Clone)] // ArgEnum here +pub enum TransactionType { + Deposit, + Transfer, + Withdrawal, +} + +impl From for Transaction { + fn from(t: TransactionType) -> Transaction { + match t { + TransactionType::Deposit => Transaction::Deposit, + TransactionType::Transfer => Transaction::Transfer, + TransactionType::Withdrawal => Transaction::Withdrawal, + } + } } pub fn run( @@ -31,6 +50,7 @@ pub fn run( min_amount, max_amount, recipient, + transaction_type, }: Args, kairos_server_address: Url, ) -> Result { @@ -41,6 +61,7 @@ pub fn run( min_amount, max_amount, recipient, + transaction_type: transaction_type.map(Into::into), }; let transactions = client::fetch(&kairos_server_address, &transaction_filter) .map_err(Into::::into)?; diff --git a/kairos-data/src/transaction.rs b/kairos-data/src/transaction.rs index 006a4514..34204a03 100644 --- a/kairos-data/src/transaction.rs +++ b/kairos-data/src/transaction.rs @@ -38,6 +38,7 @@ pub struct TransactionFilter { pub min_amount: Option, pub max_amount: Option, pub recipient: Option, + pub transaction_type: Option, } pub async fn get( @@ -67,6 +68,9 @@ pub async fn get( if let Some(recipient) = filter.recipient { query = query.filter(transactions::recipient.eq(recipient)); } + if let Some(transaction_type) = filter.transaction_type { + query = query.filter(transactions::trx.eq(transaction_type)); + } query .select(Transactions::as_select()) From c56e4b052bddee198891401de07d0db5faaf03fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Wed, 24 Jul 2024 10:01:37 +0200 Subject: [PATCH 7/9] tests/e2e: wait_for_deposit -> wait_for_transaction --- nixos/tests/end-to-end.nix | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nixos/tests/end-to-end.nix b/nixos/tests/end-to-end.nix index 29aab5e7..41957012 100644 --- a/nixos/tests/end-to-end.nix +++ b/nixos/tests/end-to-end.nix @@ -104,11 +104,11 @@ nixosTest { raise Exception("Success key not found in JSON") @backoff.on_exception(backoff.expo, Exception, max_tries=5, jitter=backoff.full_jitter) - def wait_for_deposit(depositor, amount): - transactions_result = client.succeed("kairos-cli --kairos-server-address http://kairos fetch --sender {}".format(depositor)) + def wait_for_transaction(sender, transaction_type, amount): + transactions_result = client.succeed("kairos-cli --kairos-server-address http://kairos fetch --sender {} --transaction-type {}".format(sender, transaction_type)) transactions = json.loads(transactions_result) - if not any(transaction.get("public_key") == depositor and transaction.get("amount") == str(amount) for transaction in transactions): - raise Exception("Couldn't find deposit for depositor {} with amount {} in transactions\n:{}".format(depositor, amount, transactions)) + if not any(transaction.get("public_key") == sender and transaction.get("amount") == str(amount) for transaction in transactions): + raise Exception("Couldn't find {} for sender {} with amount {} in transactions\n:{}".format(transaction_type, sender, amount, transactions)) # Test start_all() @@ -138,7 +138,7 @@ nixosTest { wait_for_successful_deploy(deposit_deploy_hash) - wait_for_deposit(depositor, 3000000000) + wait_for_transaction(depositor, "deposit", 3000000000) # transfer beneficiary = client.succeed("cat ${clientUsersDirectory}/user-3/public_key_hex") From 760627aa48e7341ec10f6da7a1bfbd6673ca574b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Wed, 24 Jul 2024 10:02:11 +0200 Subject: [PATCH 8/9] tests/e2e: test withdrawal --- nixos/tests/end-to-end.nix | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nixos/tests/end-to-end.nix b/nixos/tests/end-to-end.nix index 41957012..4ffe83f2 100644 --- a/nixos/tests/end-to-end.nix +++ b/nixos/tests/end-to-end.nix @@ -150,12 +150,17 @@ nixosTest { transactions = json.loads(transactions_result) assert any(transaction.get("recipient") == beneficiary and transaction.get("amount") == str(1000) for transaction in transactions), "Couldn't find the transfer in the L2's DA: {}".format(transactions) - # TODO test withdraw + # withdraw + withdrawer = client.succeed("cat ${clientUsersDirectory}/user-3/public_key_hex") + withdrawer_private_key = "${clientUsersDirectory}/user-3/secret_key.pem" + withdraw_output = client.succeed("kairos-cli --kairos-server-address http://kairos withdraw --amount 800 --private-key {}".format(withdrawer_private_key)) + assert "Withdrawal successfully sent to L2\n" in withdraw_output, "The withdraw command was not successful: {}".format(withdraw_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 + wait_for_transaction(withdrawer, "withdrawal", 800) # cli_output = client.succeed("kairos-cli transfer --recipient '01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356' --amount 1000 --private-key ${testResources}/secp256k1/secret_key.pem") # assert "ok\n" in cli_output From a06ae3f5b50aae9336fb8e1b3b1052ff6f9365bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Wed, 24 Jul 2024 10:02:36 +0200 Subject: [PATCH 9/9] tests/e2e: remove comments/ unused args --- nixos/tests/end-to-end.nix | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/nixos/tests/end-to-end.nix b/nixos/tests/end-to-end.nix index 4ffe83f2..c57cbbe8 100644 --- a/nixos/tests/end-to-end.nix +++ b/nixos/tests/end-to-end.nix @@ -1,7 +1,6 @@ { nixosTest , mkKairosHostConfig , kairos -, testResources ? ../../kairos-cli/tests/fixtures , kairos-contracts , cctlModule , fetchurl @@ -156,17 +155,9 @@ nixosTest { withdraw_output = client.succeed("kairos-cli --kairos-server-address http://kairos withdraw --amount 800 --private-key {}".format(withdrawer_private_key)) assert "Withdrawal successfully sent to L2\n" in withdraw_output, "The withdraw command was not successful: {}".format(withdraw_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 wait_for_transaction(withdrawer, "withdrawal", 800) - # 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 + # TODO cctl does not provide any secp256k1 keys, once support is added it should be tested here ''; }