diff --git a/Cargo.toml b/Cargo.toml index c21c9a5..4b501ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -resolver = "2" +resolver = "3" members = [ "integration_tests", "sequencer_runner", @@ -25,7 +25,7 @@ futures = "0.3" actix-rt = "*" lazy_static = "1.5.0" env_logger = "0.10" -log = "0.4" +log = "0.4.28" lru = "0.7.8" thiserror = "2.0.12" rs_merkle = "1.4" diff --git a/ci_scripts/test-ubuntu.sh b/ci_scripts/test-ubuntu.sh index 5b7afcf..6a1710b 100644 --- a/ci_scripts/test-ubuntu.sh +++ b/ci_scripts/test-ubuntu.sh @@ -7,4 +7,5 @@ source env.sh RISC0_DEV_MODE=1 cargo test --release cd integration_tests export NSSA_WALLET_HOME_DIR=$(pwd)/configs/debug/wallet/ +export RUST_LOG=info cargo run $(pwd)/configs/debug all diff --git a/common/Cargo.toml b/common/Cargo.toml index 8bfc317..dcb5f60 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "common" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] anyhow.workspace = true diff --git a/common/src/lib.rs b/common/src/lib.rs index 01ae261..3b2ddff 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -66,6 +66,8 @@ pub enum ExecutionFailureKind { ProveError(anyhow::Error), #[error("Failed to decode data from VM: {0:?}")] DecodeError(String), + #[error("Failed to get account data from sequencer")] + SequencerError, #[error("Inputs amounts does not match outputs")] AmountMismatchError, #[error("Accounts key not found")] diff --git a/common/src/merkle_tree_public/hasher.rs b/common/src/merkle_tree_public/hasher.rs index b23388f..ed9443a 100644 --- a/common/src/merkle_tree_public/hasher.rs +++ b/common/src/merkle_tree_public/hasher.rs @@ -1,5 +1,5 @@ use rs_merkle::Hasher; -use sha2::{digest::FixedOutput, Digest, Sha256}; +use sha2::{Digest, Sha256, digest::FixedOutput}; use super::TreeHashType; diff --git a/common/src/merkle_tree_public/merkle_tree.rs b/common/src/merkle_tree_public/merkle_tree.rs index e2eb715..8911344 100644 --- a/common/src/merkle_tree_public/merkle_tree.rs +++ b/common/src/merkle_tree_public/merkle_tree.rs @@ -2,14 +2,14 @@ use std::{collections::HashMap, fmt, marker::PhantomData}; use rs_merkle::{MerkleProof, MerkleTree}; use serde::{ + Deserialize, Deserializer, Serialize, de::{SeqAccess, Visitor}, ser::SerializeSeq, - Deserialize, Deserializer, Serialize, }; use crate::{transaction::Transaction, utxo_commitment::UTXOCommitment}; -use super::{hasher::OwnHasher, tree_leav_item::TreeLeavItem, TreeHashType}; +use super::{TreeHashType, hasher::OwnHasher, tree_leav_item::TreeLeavItem}; #[derive(Clone)] pub struct HashStorageMerkleTree { diff --git a/common/src/rpc_primitives/errors.rs b/common/src/rpc_primitives/errors.rs index 6bbf207..fc52dd0 100644 --- a/common/src/rpc_primitives/errors.rs +++ b/common/src/rpc_primitives/errors.rs @@ -1,4 +1,4 @@ -use serde_json::{to_value, Value}; +use serde_json::{Value, to_value}; use std::fmt; #[derive(serde::Serialize)] @@ -65,7 +65,7 @@ impl RpcError { return Self::server_error(Some(format!( "Failed to serialize invalid parameters error: {:?}", err.to_string() - ))) + ))); } }; RpcError::new(-32_602, "Invalid params".to_owned(), Some(value)) @@ -178,7 +178,7 @@ impl From for RpcError { let error_data = match to_value(&e) { Ok(value) => value, Err(_err) => { - return RpcError::new_internal_error(None, "Failed to serialize ServerError") + return RpcError::new_internal_error(None, "Failed to serialize ServerError"); } }; RpcError::new_internal_error(Some(error_data), e.to_string().as_str()) diff --git a/common/src/rpc_primitives/message.rs b/common/src/rpc_primitives/message.rs index c6bdc58..e8e4186 100644 --- a/common/src/rpc_primitives/message.rs +++ b/common/src/rpc_primitives/message.rs @@ -315,10 +315,10 @@ impl From for Vec { #[cfg(test)] mod tests { + use serde_json::Value; use serde_json::de::from_slice; use serde_json::json; use serde_json::ser::to_vec; - use serde_json::Value; use super::*; diff --git a/common/src/rpc_primitives/requests.rs b/common/src/rpc_primitives/requests.rs index 2e67f16..a566ee2 100644 --- a/common/src/rpc_primitives/requests.rs +++ b/common/src/rpc_primitives/requests.rs @@ -1,8 +1,8 @@ use crate::parse_request; use super::errors::RpcParseError; -use super::parser::parse_params; use super::parser::RpcRequest; +use super::parser::parse_params; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -43,6 +43,16 @@ pub struct GetTransactionByHashRequest { pub hash: String, } +#[derive(Serialize, Deserialize, Debug)] +pub struct GetAccountsNoncesRequest { + pub addresses: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GetAccountDataRequest { + pub address: String, +} + parse_request!(HelloRequest); parse_request!(RegisterAccountRequest); parse_request!(SendTxRequest); @@ -52,6 +62,8 @@ parse_request!(GetLastBlockRequest); parse_request!(GetInitialTestnetAccountsRequest); parse_request!(GetAccountBalanceRequest); parse_request!(GetTransactionByHashRequest); +parse_request!(GetAccountsNoncesRequest); +parse_request!(GetAccountDataRequest); #[derive(Serialize, Deserialize, Debug)] pub struct HelloResponse { @@ -66,6 +78,7 @@ pub struct RegisterAccountResponse { #[derive(Serialize, Deserialize, Debug)] pub struct SendTxResponse { pub status: String, + pub tx_hash: String, } #[derive(Serialize, Deserialize, Debug)] @@ -88,7 +101,20 @@ pub struct GetAccountBalanceResponse { pub balance: u128, } +#[derive(Serialize, Deserialize, Debug)] +pub struct GetAccountsNoncesResponse { + pub nonces: Vec, +} + #[derive(Serialize, Deserialize, Debug)] pub struct GetTransactionByHashResponse { pub transaction: Option, } + +#[derive(Serialize, Deserialize, Debug)] +pub struct GetAccountDataResponse { + pub balance: u128, + pub nonce: u128, + pub program_owner: [u32; 8], + pub data: Vec, +} diff --git a/common/src/sequencer_client/json.rs b/common/src/sequencer_client/json.rs index 71b1719..59895bf 100644 --- a/common/src/sequencer_client/json.rs +++ b/common/src/sequencer_client/json.rs @@ -12,7 +12,7 @@ pub struct SendTxRequest { #[derive(Serialize, Deserialize, Debug)] pub struct SendTxResponse { pub status: String, - pub additional_data: Option, + pub tx_hash: String, } //General diff --git a/common/src/sequencer_client/mod.rs b/common/src/sequencer_client/mod.rs index a131033..ad66d82 100644 --- a/common/src/sequencer_client/mod.rs +++ b/common/src/sequencer_client/mod.rs @@ -7,6 +7,10 @@ use json::{SendTxRequest, SendTxResponse, SequencerRpcRequest, SequencerRpcRespo use reqwest::Client; use serde_json::Value; +use crate::rpc_primitives::requests::{ + GetAccountsNoncesRequest, GetAccountsNoncesResponse, GetTransactionByHashRequest, + GetTransactionByHashResponse, +}; use crate::sequencer_client::json::AccountInitialData; use crate::{SequencerClientError, SequencerRpcError}; @@ -86,6 +90,42 @@ impl SequencerClient { Ok(resp_deser) } + ///Get accounts nonces for `addresses`. `addresses` must be a list of valid hex-strings for 32 bytes. + pub async fn get_accounts_nonces( + &self, + addresses: Vec, + ) -> Result { + let block_req = GetAccountsNoncesRequest { addresses }; + + let req = serde_json::to_value(block_req)?; + + let resp = self + .call_method_with_payload("get_accounts_nonces", req) + .await?; + + let resp_deser = serde_json::from_value(resp)?; + + Ok(resp_deser) + } + + ///Get transaction details for `hash`. + pub async fn get_transaction_by_hash( + &self, + hash: String, + ) -> Result { + let block_req = GetTransactionByHashRequest { hash }; + + let req = serde_json::to_value(block_req)?; + + let resp = self + .call_method_with_payload("get_transaction_by_hash", req) + .await?; + + let resp_deser = serde_json::from_value(resp)?; + + Ok(resp_deser) + } + ///Send transaction to sequencer pub async fn send_tx( &self, diff --git a/common/src/transaction.rs b/common/src/transaction.rs index afc4559..1d0ccec 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -1,12 +1,12 @@ use k256::ecdsa::{ - signature::{Signer, Verifier}, Signature, SigningKey, VerifyingKey, + signature::{Signer, Verifier}, }; use log::info; use secp256k1_zkp::{PedersenCommitment, Tweak}; use serde::{Deserialize, Serialize}; -use sha2::{digest::FixedOutput, Digest}; +use sha2::{Digest, digest::FixedOutput}; use crate::merkle_tree_public::TreeHashType; @@ -297,9 +297,9 @@ impl AuthenticatedTransaction { #[cfg(test)] mod tests { use super::*; - use k256::{ecdsa::signature::Signer, FieldBytes}; - use secp256k1_zkp::{constants::SECRET_KEY_SIZE, Tweak}; - use sha2::{digest::FixedOutput, Digest}; + use k256::{FieldBytes, ecdsa::signature::Signer}; + use secp256k1_zkp::{Tweak, constants::SECRET_KEY_SIZE}; + use sha2::{Digest, digest::FixedOutput}; use crate::{ merkle_tree_public::TreeHashType, @@ -378,11 +378,13 @@ mod tests { assert_eq!(authenticated_tx.transaction(), &transaction); assert_eq!(hash, &transaction.body.hash()); - assert!(authenticated_tx - .transaction() - .public_key - .verify(&transaction.body.to_bytes(), &signature) - .is_ok()); + assert!( + authenticated_tx + .transaction() + .public_key + .verify(&transaction.body.to_bytes(), &signature) + .is_ok() + ); } #[test] diff --git a/integration_tests/configs/debug/wallet/wallet_config.json b/integration_tests/configs/debug/wallet/wallet_config.json index d888119..e1edc05 100644 --- a/integration_tests/configs/debug/wallet/wallet_config.json +++ b/integration_tests/configs/debug/wallet/wallet_config.json @@ -2,7 +2,10 @@ "home": "./node", "override_rust_log": null, "sequencer_addr": "http://127.0.0.1:3040", - "seq_poll_timeout_secs": 10, + "seq_poll_timeout_millis": 12000, + "seq_poll_max_blocks": 5, + "seq_poll_max_retries": 5, + "seq_poll_retry_delay_millis": 500, "initial_accounts": [ { "address": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 812e44f..9b7b970 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -4,12 +4,15 @@ use actix_web::dev::ServerHandle; use anyhow::Result; use clap::Parser; use common::sequencer_client::SequencerClient; -use log::info; +use log::{info, warn}; use sequencer_core::config::SequencerConfig; use sequencer_runner::startup_sequencer; use tempfile::TempDir; use tokio::task::JoinHandle; -use wallet::{Command, helperfunctions::fetch_config}; +use wallet::{ + Command, + helperfunctions::{fetch_config, fetch_persistent_accounts}, +}; #[derive(Parser, Debug)] #[clap(version)] @@ -66,6 +69,14 @@ pub async fn post_test(residual: (ServerHandle, JoinHandle>, TempDir) sequencer_loop_handle.abort(); seq_http_server_handle.stop(true).await; + let wallet_home = wallet::helperfunctions::get_home().unwrap(); + let persistent_data_home = wallet_home.join("curr_accounts.json"); + + //Removing persistent accounts after run to not affect other executions + //Not necessary an error, if fails as there is tests for failure scenario + let _ = std::fs::remove_file(persistent_data_home) + .inspect_err(|err| warn!("Failed to remove persistent data with err {err:#?}")); + //At this point all of the references to sequencer_core must be lost. //So they are dropped and tempdirs will be dropped too, } @@ -73,7 +84,6 @@ pub async fn post_test(residual: (ServerHandle, JoinHandle>, TempDir) pub async fn test_success() { let command = Command::SendNativeTokenTransfer { from: ACC_SENDER.to_string(), - nonce: 0, to: ACC_RECEIVER.to_string(), amount: 100, }; @@ -107,14 +117,7 @@ pub async fn test_success() { } pub async fn test_success_move_to_another_account() { - let hex_acc_receiver_new_acc = hex::encode([42; 32]); - - let command = Command::SendNativeTokenTransfer { - from: ACC_SENDER.to_string(), - nonce: 0, - to: hex_acc_receiver_new_acc.clone(), - amount: 100, - }; + let command = Command::RegisterAccount {}; let wallet_config = fetch_config().unwrap(); @@ -122,6 +125,30 @@ pub async fn test_success_move_to_another_account() { wallet::execute_subcommand(command).await.unwrap(); + let persistent_accounts = fetch_persistent_accounts().unwrap(); + + let mut new_persistent_account_addr = String::new(); + + for per_acc in persistent_accounts { + if (per_acc.address.to_string() != ACC_RECEIVER) + && (per_acc.address.to_string() != ACC_SENDER) + { + new_persistent_account_addr = per_acc.address.to_string(); + } + } + + if new_persistent_account_addr == String::new() { + panic!("Failed to produce new account, not present in persistent accounts"); + } + + let command = Command::SendNativeTokenTransfer { + from: ACC_SENDER.to_string(), + to: new_persistent_account_addr.clone(), + amount: 100, + }; + + wallet::execute_subcommand(command).await.unwrap(); + info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; @@ -131,7 +158,7 @@ pub async fn test_success_move_to_another_account() { .await .unwrap(); let acc_2_balance = seq_client - .get_account_balance(hex_acc_receiver_new_acc) + .get_account_balance(new_persistent_account_addr) .await .unwrap(); @@ -147,7 +174,6 @@ pub async fn test_success_move_to_another_account() { pub async fn test_failure() { let command = Command::SendNativeTokenTransfer { from: ACC_SENDER.to_string(), - nonce: 0, to: ACC_RECEIVER.to_string(), amount: 1000000, }; @@ -185,7 +211,6 @@ pub async fn test_failure() { pub async fn test_success_two_transactions() { let command = Command::SendNativeTokenTransfer { from: ACC_SENDER.to_string(), - nonce: 0, to: ACC_RECEIVER.to_string(), amount: 100, }; @@ -219,7 +244,6 @@ pub async fn test_success_two_transactions() { let command = Command::SendNativeTokenTransfer { from: ACC_SENDER.to_string(), - nonce: 1, to: ACC_RECEIVER.to_string(), amount: 100, }; diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index d624505..3ce874d 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "key_protocol" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] anyhow.workspace = true diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index 108a41e..55120a6 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -1,7 +1,11 @@ +use aes_gcm::{AeadCore, Aes256Gcm, KeyInit, aead::Aead}; use elliptic_curve::PrimeField; -use k256::{AffinePoint, Scalar}; +use elliptic_curve::point::AffineCoordinates; +use k256::{AffinePoint, FieldBytes, Scalar}; use log::info; -use sha2::Digest; +use rand::{RngCore, rngs::OsRng}; + +use super::constants_types::{CipherText, Nonce}; #[derive(Debug)] ///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index d3a4b08..9fc3f06 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -114,8 +114,8 @@ impl KeyChain { #[cfg(test)] mod tests { use aes_gcm::{ - aead::{Aead, KeyInit, OsRng}, Aes256Gcm, + aead::{Aead, KeyInit, OsRng}, }; use elliptic_curve::ff::Field; use elliptic_curve::group::prime::PrimeCurveAffine; diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 5180efb..9387747 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -4,7 +4,7 @@ use elliptic_curve::PrimeField; use k256::{AffinePoint, Scalar}; use rand::{rngs::OsRng, RngCore}; use serde::{Deserialize, Serialize}; -use sha2::{digest::FixedOutput, Digest}; +use sha2::{Digest, digest::FixedOutput}; #[derive(Debug)] ///Seed holder. Non-clonable to ensure that different holders use different seeds. diff --git a/mempool/Cargo.toml b/mempool/Cargo.toml index 0d1a317..c47d2b0 100644 --- a/mempool/Cargo.toml +++ b/mempool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mempool" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] diff --git a/nssa/core/src/commitment.rs b/nssa/core/src/commitment.rs index bc22c8f..1eb8c98 100644 --- a/nssa/core/src/commitment.rs +++ b/nssa/core/src/commitment.rs @@ -8,6 +8,8 @@ use crate::{NullifierPublicKey, account::Account}; pub struct Commitment(pub(super) [u8; 32]); impl Commitment { + /// Generates the commitment to a private account owned by user for npk: + /// SHA256(npk || program_owner || balance || nonce || data) pub fn new(npk: &NullifierPublicKey, account: &Account) -> Self { let mut bytes = Vec::new(); bytes.extend_from_slice(&npk.to_byte_array()); @@ -34,6 +36,7 @@ pub type CommitmentSetDigest = [u8; 32]; pub type MembershipProof = (usize, Vec<[u8; 32]>); +/// Computes the resulting digest for the given membership proof and corresponding commitment pub fn compute_digest_for_path( commitment: &Commitment, proof: &MembershipProof, diff --git a/nssa/program_methods/Cargo.toml b/nssa/program_methods/Cargo.toml index af52307..40dab21 100644 --- a/nssa/program_methods/Cargo.toml +++ b/nssa/program_methods/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "program-methods" version = "0.1.0" -edition = "2021" +edition = "2024" [build-dependencies] risc0-build = { version = "3.0.3" } diff --git a/nssa/program_methods/guest/Cargo.toml b/nssa/program_methods/guest/Cargo.toml index 0d47ccd..da4dbe8 100644 --- a/nssa/program_methods/guest/Cargo.toml +++ b/nssa/program_methods/guest/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "programs" version = "0.1.0" -edition = "2021" +edition = "2024" [workspace] diff --git a/nssa/src/merkle_tree/mod.rs b/nssa/src/merkle_tree/mod.rs index 2306efd..7b30d78 100644 --- a/nssa/src/merkle_tree/mod.rs +++ b/nssa/src/merkle_tree/mod.rs @@ -39,11 +39,11 @@ impl MerkleTree { if tree_depth == capacity_depth { 0 } else { + // 2^(capacity_depth - tree_depth) - 1 (1 << (capacity_depth - tree_depth)) - 1 } } - - /// Number of levels required to hold all values + /// Number of levels required to hold all nodes fn depth(&self) -> usize { self.length.next_power_of_two().trailing_zeros() as usize } @@ -57,6 +57,7 @@ impl MerkleTree { } pub fn with_capacity(capacity: usize) -> Self { + // Adjust capacity to ensure power of two let capacity = capacity.next_power_of_two(); let total_depth = capacity.trailing_zeros() as usize; @@ -75,6 +76,8 @@ impl MerkleTree { } } + /// Reallocates storage of Merkle tree for double capacity. + /// The current tree is embedded into the new tree as a subtree fn reallocate_to_double_capacity(&mut self) { let old_capacity = self.capacity; let new_capacity = old_capacity << 1; @@ -102,9 +105,11 @@ impl MerkleTree { let mut node_index = new_index + self.capacity - 1; let mut node_hash = hash_value(&value); + // Insert the new node at the bottom layer self.set_node(node_index, node_hash); self.length += 1; + // Update upper levels for the newly inserted node for _ in 0..self.depth() { let parent_index = (node_index - 1) >> 1; let left_child = self.get_node((parent_index << 1) + 1); @@ -129,6 +134,7 @@ impl MerkleTree { while node_index != root_index { let parent_index = (node_index - 1) >> 1; + // Left children have odd indices, and right children have even indices let is_left_child = node_index & 1 == 1; let sibling_index = if is_left_child { node_index + 1 diff --git a/nssa/src/state.rs b/nssa/src/state.rs index f975804..f980370 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -21,6 +21,7 @@ impl CommitmentSet { self.merkle_tree.root() } + /// Queries the `CommitmentSet` for a membership proof of commitment pub fn get_proof_for(&self, commitment: &Commitment) -> Option { let index = *self.commitments.get(commitment)?; @@ -29,6 +30,7 @@ impl CommitmentSet { .map(|path| (index, path)) } + /// Inserts a list of commitments to the `CommitmentSet`. pub(crate) fn extend(&mut self, commitments: &[Commitment]) { for commitment in commitments.iter().cloned() { let index = self.merkle_tree.insert(commitment.to_byte_array()); @@ -41,6 +43,9 @@ impl CommitmentSet { self.commitments.contains_key(commitment) } + /// Initializes an empty `CommitmentSet` with a given capacity. + /// If the capacity is not a power_of_two, then capacity is taken + /// to be the next power_of_two. pub(crate) fn with_capacity(capacity: usize) -> CommitmentSet { Self { merkle_tree: MerkleTree::with_capacity(capacity), diff --git a/nssa/test_program_methods/Cargo.toml b/nssa/test_program_methods/Cargo.toml index 50d6ca2..0317d2b 100644 --- a/nssa/test_program_methods/Cargo.toml +++ b/nssa/test_program_methods/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "test-program-methods" version = "0.1.0" -edition = "2021" +edition = "2024" [build-dependencies] risc0-build = { version = "3.0.3" } diff --git a/nssa/test_program_methods/guest/Cargo.toml b/nssa/test_program_methods/guest/Cargo.toml index ea669c0..2289292 100644 --- a/nssa/test_program_methods/guest/Cargo.toml +++ b/nssa/test_program_methods/guest/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "programs" version = "0.1.0" -edition = "2021" +edition = "2024" [workspace] diff --git a/rustfmt.toml b/rustfmt.toml index 3a26366..f216078 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1 @@ -edition = "2021" +edition = "2024" diff --git a/sequencer_core/Cargo.toml b/sequencer_core/Cargo.toml index 0e85b71..1c56f11 100644 --- a/sequencer_core/Cargo.toml +++ b/sequencer_core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sequencer_core" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] hex.workspace = true @@ -9,6 +9,7 @@ anyhow.workspace = true serde.workspace = true rand.workspace = true tempfile.workspace = true +log.workspace = true [dependencies.storage] path = "../storage" diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index b56b34cc..2137c8b 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -3,6 +3,7 @@ use std::fmt::Display; use anyhow::Result; use common::{block::HashableBlockData, merkle_tree_public::TreeHashType}; use config::SequencerConfig; +use log::warn; use mempool::MemPool; use sequencer_store::SequecerChainStore; use serde::{Deserialize, Serialize}; @@ -78,7 +79,9 @@ impl SequencerCore { return Err(TransactionMalformationErrorKind::MempoolFullForRound); } - let authenticated_tx = self.transaction_pre_check(transaction)?; + let authenticated_tx = self + .transaction_pre_check(transaction) + .inspect_err(|err| warn!("Error at pre_check {err:#?}"))?; self.mempool.push_item(authenticated_tx); @@ -89,7 +92,10 @@ impl SequencerCore { &mut self, tx: nssa::PublicTransaction, ) -> Result { - self.store.state.transition_from_public_transaction(&tx)?; + self.store + .state + .transition_from_public_transaction(&tx) + .inspect_err(|err| warn!("Error at transition {err:#?}"))?; Ok(tx) } diff --git a/sequencer_core/src/sequencer_store/mod.rs b/sequencer_core/src/sequencer_store/mod.rs index 4254ed7..bb0a7c1 100644 --- a/sequencer_core/src/sequencer_store/mod.rs +++ b/sequencer_core/src/sequencer_store/mod.rs @@ -3,7 +3,7 @@ use std::path::Path; use block_store::SequecerBlockStore; use common::block::HashableBlockData; use nssa::{self, Address}; -use rand::{rngs::OsRng, RngCore}; +use rand::{RngCore, rngs::OsRng}; use crate::config::AccountInitialData; diff --git a/sequencer_rpc/Cargo.toml b/sequencer_rpc/Cargo.toml index 132c0f1..7972342 100644 --- a/sequencer_rpc/Cargo.toml +++ b/sequencer_rpc/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sequencer_rpc" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] anyhow.workspace = true diff --git a/sequencer_rpc/src/lib.rs b/sequencer_rpc/src/lib.rs index 1112dbd..2973c42 100644 --- a/sequencer_rpc/src/lib.rs +++ b/sequencer_rpc/src/lib.rs @@ -5,8 +5,8 @@ pub mod types; use std::sync::Arc; use common::rpc_primitives::{ - errors::{RpcError, RpcErrorKind}, RpcPollingConfig, + errors::{RpcError, RpcErrorKind}, }; use sequencer_core::SequencerCore; use serde::Serialize; diff --git a/sequencer_rpc/src/net_utils.rs b/sequencer_rpc/src/net_utils.rs index c421f17..b373823 100644 --- a/sequencer_rpc/src/net_utils.rs +++ b/sequencer_rpc/src/net_utils.rs @@ -2,13 +2,13 @@ use std::io; use std::sync::Arc; use actix_cors::Cors; -use actix_web::{http, middleware, web, App, Error as HttpError, HttpResponse, HttpServer}; +use actix_web::{App, Error as HttpError, HttpResponse, HttpServer, http, middleware, web}; use futures::Future; use futures::FutureExt; use log::info; -use common::rpc_primitives::message::Message; use common::rpc_primitives::RpcConfig; +use common::rpc_primitives::message::Message; use sequencer_core::SequencerCore; use tokio::sync::Mutex; diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index 2d5a29a..2f55dd2 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -1,5 +1,5 @@ use actix_web::Error as HttpError; -use base64::{engine::general_purpose, Engine}; +use base64::{Engine, engine::general_purpose}; use nssa; use sequencer_core::config::AccountInitialData; use serde_json::Value; @@ -12,8 +12,10 @@ use common::{ message::{Message, Request}, parser::RpcRequest, requests::{ - GetAccountBalanceRequest, GetAccountBalanceResponse, GetInitialTestnetAccountsRequest, - GetTransactionByHashRequest, GetTransactionByHashResponse, + GetAccountBalanceRequest, GetAccountBalanceResponse, GetAccountDataRequest, + GetAccountDataResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse, + GetInitialTestnetAccountsRequest, GetTransactionByHashRequest, + GetTransactionByHashResponse, }, }, }; @@ -24,7 +26,7 @@ use common::rpc_primitives::requests::{ SendTxResponse, }; -use super::{respond, types::err_rpc::RpcErr, JsonHandler}; +use super::{JsonHandler, respond, types::err_rpc::RpcErr}; pub const HELLO: &str = "hello"; pub const SEND_TX: &str = "send_tx"; @@ -33,10 +35,12 @@ pub const GET_GENESIS: &str = "get_genesis"; pub const GET_LAST_BLOCK: &str = "get_last_block"; pub const GET_ACCOUNT_BALANCE: &str = "get_account_balance"; pub const GET_TRANSACTION_BY_HASH: &str = "get_transaction_by_hash"; +pub const GET_ACCOUNTS_NONCES: &str = "get_accounts_nonces"; +pub const GET_ACCOUNT_DATA: &str = "get_account_data"; pub const HELLO_FROM_SEQUENCER: &str = "HELLO_FROM_SEQUENCER"; -pub const SUCCESS: &str = "Success"; +pub const TRANSACTION_SUBMITTED: &str = "Transaction submitted"; pub const GET_INITIAL_TESTNET_ACCOUNTS: &str = "get_initial_testnet_accounts"; @@ -72,6 +76,7 @@ impl JsonHandler { let send_tx_req = SendTxRequest::parse(Some(request.params))?; let tx = nssa::PublicTransaction::from_bytes(&send_tx_req.transaction) .map_err(|e| RpcError::serialization_error(&e.to_string()))?; + let tx_hash = hex::encode(tx.hash()); { let mut state = self.sequencer_state.lock().await; @@ -80,7 +85,8 @@ impl JsonHandler { } let helperstruct = SendTxResponse { - status: SUCCESS.to_string(), + status: TRANSACTION_SUBMITTED.to_string(), + tx_hash, }; respond(helperstruct) @@ -171,6 +177,59 @@ impl JsonHandler { respond(helperstruct) } + /// Returns the nonces of the accounts at the given addresses. + /// Each address must be a valid hex string of the correct length. + async fn process_get_accounts_nonces(&self, request: Request) -> Result { + let get_account_nonces_req = GetAccountsNoncesRequest::parse(Some(request.params))?; + let mut addresses = vec![]; + for address_raw in get_account_nonces_req.addresses { + let address = address_raw + .parse::() + .map_err(|e| RpcError::invalid_params(e.to_string()))?; + + addresses.push(address); + } + + let nonces = { + let state = self.sequencer_state.lock().await; + + addresses + .into_iter() + .map(|addr| state.store.state.get_account_by_address(&addr).nonce) + .collect() + }; + + let helperstruct = GetAccountsNoncesResponse { nonces }; + + respond(helperstruct) + } + + ///Returns account struct for give address. + /// Address must be a valid hex string of the correct length. + async fn process_get_account_data(&self, request: Request) -> Result { + let get_account_nonces_req = GetAccountDataRequest::parse(Some(request.params))?; + + let address = get_account_nonces_req + .address + .parse::() + .map_err(|e| RpcError::invalid_params(e.to_string()))?; + + let account = { + let state = self.sequencer_state.lock().await; + + state.store.state.get_account_by_address(&address) + }; + + let helperstruct = GetAccountDataResponse { + balance: account.balance, + nonce: account.nonce, + program_owner: account.program_owner, + data: account.data, + }; + + respond(helperstruct) + } + /// Returns the transaction corresponding to the given hash, if it exists in the blockchain. /// The hash must be a valid hex string of the correct length. async fn process_get_transaction_by_hash(&self, request: Request) -> Result { @@ -205,6 +264,8 @@ impl JsonHandler { GET_LAST_BLOCK => self.process_get_last_block(request).await, GET_INITIAL_TESTNET_ACCOUNTS => self.get_initial_testnet_accounts(request).await, GET_ACCOUNT_BALANCE => self.process_get_account_balance(request).await, + GET_ACCOUNTS_NONCES => self.process_get_accounts_nonces(request).await, + GET_ACCOUNT_DATA => self.process_get_account_data(request).await, GET_TRANSACTION_BY_HASH => self.process_get_transaction_by_hash(request).await, _ => Err(RpcErr(RpcError::method_not_found(request.method))), } @@ -215,13 +276,13 @@ impl JsonHandler { mod tests { use std::sync::Arc; - use crate::{rpc_handler, JsonHandler}; - use base64::{engine::general_purpose, Engine}; + use crate::{JsonHandler, rpc_handler}; + use base64::{Engine, engine::general_purpose}; use common::rpc_primitives::RpcPollingConfig; use sequencer_core::{ - config::{AccountInitialData, SequencerConfig}, SequencerCore, + config::{AccountInitialData, SequencerConfig}, }; use serde_json::Value; use tempfile::tempdir; @@ -307,7 +368,7 @@ mod tests { } async fn call_rpc_handler_with_json(handler: JsonHandler, request_json: Value) -> Value { - use actix_web::{test, web, App}; + use actix_web::{App, test, web}; let app = test::init_service( App::new() @@ -420,6 +481,79 @@ mod tests { assert_eq!(response, expected_response); } + #[actix_web::test] + async fn test_get_accounts_nonces_for_non_existent_account() { + let (json_handler, _, _) = components_for_tests(); + let request = serde_json::json!({ + "jsonrpc": "2.0", + "method": "get_accounts_nonces", + "params": { "addresses": ["efac".repeat(16)] }, + "id": 1 + }); + let expected_response = serde_json::json!({ + "id": 1, + "jsonrpc": "2.0", + "result": { + "nonces": [ 0 ] + } + }); + + let response = call_rpc_handler_with_json(json_handler, request).await; + + assert_eq!(response, expected_response); + } + + #[actix_web::test] + async fn test_get_accounts_nonces_for_existent_account() { + let (json_handler, initial_accounts, _) = components_for_tests(); + + let acc_1_addr = initial_accounts[0].addr.clone(); + let acc_2_addr = initial_accounts[1].addr.clone(); + + let request = serde_json::json!({ + "jsonrpc": "2.0", + "method": "get_accounts_nonces", + "params": { "addresses": [acc_1_addr, acc_2_addr] }, + "id": 1 + }); + let expected_response = serde_json::json!({ + "id": 1, + "jsonrpc": "2.0", + "result": { + "nonces": [ 1, 0 ] + } + }); + + let response = call_rpc_handler_with_json(json_handler, request).await; + + assert_eq!(response, expected_response); + } + + #[actix_web::test] + async fn test_get_account_data_for_non_existent_account() { + let (json_handler, _, _) = components_for_tests(); + let request = serde_json::json!({ + "jsonrpc": "2.0", + "method": "get_account_data", + "params": { "address": "efac".repeat(16) }, + "id": 1 + }); + let expected_response = serde_json::json!({ + "id": 1, + "jsonrpc": "2.0", + "result": { + "balance": 0, + "nonce": 0, + "program_owner": [ 0, 0, 0, 0, 0, 0, 0, 0], + "data": [], + } + }); + + let response = call_rpc_handler_with_json(json_handler, request).await; + + assert_eq!(response, expected_response); + } + #[actix_web::test] async fn test_get_transaction_by_hash_for_non_existent_hash() { let (json_handler, _, _) = components_for_tests(); diff --git a/sequencer_runner/Cargo.toml b/sequencer_runner/Cargo.toml index 2ca4e23..4da788d 100644 --- a/sequencer_runner/Cargo.toml +++ b/sequencer_runner/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sequencer_runner" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] anyhow.workspace = true diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index 625350b..b682a18 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -5,7 +5,7 @@ use anyhow::Result; use clap::Parser; use common::rpc_primitives::RpcConfig; use log::info; -use sequencer_core::{config::SequencerConfig, SequencerCore}; +use sequencer_core::{SequencerCore, config::SequencerConfig}; use sequencer_rpc::new_http_server; use tokio::{sync::Mutex, task::JoinHandle}; @@ -71,7 +71,9 @@ pub async fn main_runner() -> Result<()> { if let Some(ref rust_log) = app_config.override_rust_log { info!("RUST_LOG env var set to {rust_log:?}"); - std::env::set_var(RUST_LOG, rust_log); + unsafe { + std::env::set_var(RUST_LOG, rust_log); + } } //ToDo: Add restart on failures diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 047b20b..604b2fe 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "storage" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] anyhow.workspace = true diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 0dd12b7..9ead317 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -5,7 +5,7 @@ use error::DbError; use rocksdb::{ BoundColumnFamily, ColumnFamilyDescriptor, DBWithThreadMode, MultiThreaded, Options, }; -use sc_db_utils::{produce_blob_from_fit_vec, DataBlob, DataBlobChangeVariant}; +use sc_db_utils::{DataBlob, DataBlobChangeVariant, produce_blob_from_fit_vec}; pub mod error; pub mod sc_db_utils; diff --git a/storage/src/sc_db_utils.rs b/storage/src/sc_db_utils.rs index c86828e..af982cf 100644 --- a/storage/src/sc_db_utils.rs +++ b/storage/src/sc_db_utils.rs @@ -1,4 +1,4 @@ -use serde::{de::Error, Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de::Error}; use crate::SC_DATA_BLOB_SIZE; diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 0ecb0b0..2caa7ff 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "wallet" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] anyhow.workspace = true @@ -13,6 +13,7 @@ tokio.workspace = true tempfile.workspace = true clap.workspace = true nssa-core = { path = "../nssa/core" } +base64.workspace = true [dependencies.key_protocol] path = "../key_protocol" diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index e68b4a8..e08650c 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -4,7 +4,7 @@ use anyhow::Result; use common::merkle_tree_public::merkle_tree::UTXOCommitmentsMerkleTree; use key_protocol::key_protocol_core::NSSAUserData; -use crate::config::WalletConfig; +use crate::config::{PersistentAccountData, WalletConfig}; pub struct WalletChainStore { pub user_data: NSSAUserData, @@ -29,6 +29,13 @@ impl WalletChainStore { wallet_config: config, }) } + + pub(crate) fn insert_account_data(&mut self, acc_data: PersistentAccountData) { + self.user_data + .key_holder + .pub_account_signing_keys + .insert(acc_data.address, acc_data.pub_sign_key); + } } #[cfg(test)] @@ -72,7 +79,10 @@ mod tests { home, override_rust_log: None, sequencer_addr: "http://127.0.0.1".to_string(), - seq_poll_timeout_secs: 1, + seq_poll_timeout_millis: 12000, + seq_poll_max_blocks: 5, + seq_poll_max_retries: 10, + seq_poll_retry_delay_millis: 500, initial_accounts: create_initial_accounts(), } } diff --git a/wallet/src/config.rs b/wallet/src/config.rs index 21bd912..b6355ce 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -8,6 +8,12 @@ pub struct InitialAccountData { pub pub_sign_key: nssa::PrivateKey, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PersistentAccountData { + pub address: nssa::Address, + pub pub_sign_key: nssa::PrivateKey, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GasConfig { /// Gas spent per deploying one byte of data @@ -34,8 +40,14 @@ pub struct WalletConfig { pub override_rust_log: Option, ///Sequencer URL pub sequencer_addr: String, - ///Sequencer polling duration for new blocks in seconds - pub seq_poll_timeout_secs: u64, + ///Sequencer polling duration for new blocks in milliseconds + pub seq_poll_timeout_millis: u64, + ///Sequencer polling max number of blocks + pub seq_poll_max_blocks: usize, + ///Sequencer polling max number error retries + pub seq_poll_max_retries: u64, + ///Sequencer polling error retry delay in milliseconds + pub seq_poll_retry_delay_millis: u64, ///Initial accounts for wallet pub initial_accounts: Vec, } diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 51b9d9d..8a7b7b6 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -1,11 +1,12 @@ use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr}; use anyhow::Result; +use key_protocol::key_protocol_core::NSSAUserData; use nssa::Address; use crate::{ - config::{InitialAccountData, WalletConfig}, HOME_DIR_ENV_VAR, + config::{PersistentAccountData, WalletConfig}, }; ///Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed. @@ -30,7 +31,7 @@ pub fn produce_account_addr_from_hex(hex_str: String) -> Result
{ ///Fetch list of accounts stored at `NSSA_WALLET_HOME_DIR/curr_accounts.json` /// /// If file not present, it is considered as empty list of persistent accounts -pub fn fetch_persistent_accounts() -> Result> { +pub fn fetch_persistent_accounts() -> Result> { let home = get_home()?; let accs_path = home.join("curr_accounts.json"); @@ -47,3 +48,37 @@ pub fn fetch_persistent_accounts() -> Result> { }, } } + +///Produces a list of accounts for storage +pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec { + let mut vec_for_storage = vec![]; + + for (addr, key) in &user_data.key_holder.pub_account_signing_keys { + vec_for_storage.push(PersistentAccountData { + address: *addr, + pub_sign_key: key.clone(), + }); + } + + vec_for_storage +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_home_get_env_var() { + unsafe { + std::env::set_var(HOME_DIR_ENV_VAR, "/path/to/configs"); + } + + let home = get_home().unwrap(); + + assert_eq!(PathBuf::from_str("/path/to/configs").unwrap(), home); + + unsafe { + std::env::remove_var(HOME_DIR_ENV_VAR); + } + } +} diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 1cbbb94..a52b1c6 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -1,8 +1,9 @@ -use std::sync::Arc; +use std::{fs::File, io::Write, path::PathBuf, str::FromStr, sync::Arc}; +use base64::Engine; use common::{ - sequencer_client::{json::SendTxResponse, SequencerClient}, ExecutionFailureKind, + sequencer_client::{SequencerClient, json::SendTxResponse}, }; use anyhow::Result; @@ -14,32 +15,62 @@ use nssa::Address; use clap::{Parser, Subcommand}; use nssa_core::account::Account; -use crate::helperfunctions::{fetch_config, produce_account_addr_from_hex}; +use crate::{ + helperfunctions::{ + fetch_config, fetch_persistent_accounts, get_home, produce_account_addr_from_hex, + produce_data_for_storage, + }, + poller::TxPoller, +}; pub const HOME_DIR_ENV_VAR: &str = "NSSA_WALLET_HOME_DIR"; -pub const BLOCK_GEN_DELAY_SECS: u64 = 20; pub mod chain_storage; pub mod config; pub mod helperfunctions; +pub mod poller; pub struct WalletCore { pub storage: WalletChainStore, + pub poller: TxPoller, pub sequencer_client: Arc, } impl WalletCore { pub fn start_from_config_update_chain(config: WalletConfig) -> Result { let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?); + let tx_poller = TxPoller::new(config.clone(), client.clone()); - let storage = WalletChainStore::new(config)?; + let mut storage = WalletChainStore::new(config)?; + + let persistent_accounts = fetch_persistent_accounts()?; + for pers_acc_data in persistent_accounts { + storage.insert_account_data(pers_acc_data); + } Ok(Self { storage, + poller: tx_poller, sequencer_client: client.clone(), }) } + ///Store persistent accounts at home + pub fn store_persistent_accounts(&self) -> Result { + let home = get_home()?; + let accs_path = home.join("curr_accounts.json"); + + let data = produce_data_for_storage(&self.storage.user_data); + let accs = serde_json::to_vec_pretty(&data)?; + + let mut accs_file = File::create(accs_path.as_path())?; + accs_file.write_all(&accs)?; + + info!("Stored accounts data at {accs_path:#?}"); + + Ok(accs_path) + } + pub fn create_new_account(&mut self) -> Address { self.storage .user_data @@ -58,44 +89,75 @@ impl WalletCore { pub async fn send_public_native_token_transfer( &self, from: Address, - nonce: u128, to: Address, balance_to_move: u128, ) -> Result { - let account = self.search_for_initial_account(from); + let Ok(balance) = self.get_account_balance(from).await else { + return Err(ExecutionFailureKind::SequencerError); + }; - if let Some(account) = account { - if account.balance >= balance_to_move { - let addresses = vec![from, to]; - let nonces = vec![nonce]; - let program_id = nssa::program::Program::authenticated_transfer_program().id(); - let message = nssa::public_transaction::Message::try_new( - program_id, - addresses, - nonces, - balance_to_move, - ) - .unwrap(); + if balance >= balance_to_move { + let Ok(nonces) = self.get_accounts_nonces(vec![from]).await else { + return Err(ExecutionFailureKind::SequencerError); + }; - let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + let addresses = vec![from, to]; + let program_id = nssa::program::Program::authenticated_transfer_program().id(); + let message = nssa::public_transaction::Message::try_new( + program_id, + addresses, + nonces, + balance_to_move, + ) + .unwrap(); - if let Some(signing_key) = signing_key { - let witness_set = - nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); + let signing_key = self.storage.user_data.get_account_signing_key(&from); - let tx = nssa::PublicTransaction::new(message, witness_set); + let Some(signing_key) = signing_key else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; - Ok(self.sequencer_client.send_tx(tx).await?) - } else { - Err(ExecutionFailureKind::KeyNotFoundError) - } - } else { - Err(ExecutionFailureKind::InsufficientFundsError) - } + let witness_set = + nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx(tx).await?) } else { - Err(ExecutionFailureKind::AmountMismatchError) + Err(ExecutionFailureKind::InsufficientFundsError) } } + + ///Get account balance + pub async fn get_account_balance(&self, acc: Address) -> Result { + Ok(self + .sequencer_client + .get_account_balance(acc.to_string()) + .await? + .balance) + } + + ///Get accounts nonces + pub async fn get_accounts_nonces(&self, accs: Vec
) -> Result> { + Ok(self + .sequencer_client + .get_accounts_nonces(accs.into_iter().map(|acc| acc.to_string()).collect()) + .await? + .nonces) + } + + ///Poll transactions + pub async fn poll_public_native_token_transfer( + &self, + hash: String, + ) -> Result { + let transaction_encoded = self.poller.poll_tx(hash).await?; + let tx_base64_decode = + base64::engine::general_purpose::STANDARD.decode(transaction_encoded)?; + let pub_tx = nssa::PublicTransaction::from_bytes(&tx_base64_decode)?; + + Ok(pub_tx) + } } ///Represents CLI command for a wallet @@ -107,9 +169,6 @@ pub enum Command { ///from - valid 32 byte hex string #[arg(long)] from: String, - ///nonce - u128 integer - #[arg(long)] - nonce: u128, ///to - valid 32 byte hex string #[arg(long)] to: String, @@ -117,6 +176,23 @@ pub enum Command { #[arg(long)] amount: u128, }, + ///Register new account + RegisterAccount {}, + ///Fetch transaction by `hash` + FetchTx { + #[arg(short, long)] + tx_hash: String, + }, + ///Get account `addr` balance + GetAccountBalance { + #[arg(short, long)] + addr: String, + }, + ///Get account `addr` nonce + GetAccountNonce { + #[arg(short, long)] + addr: String, + }, } ///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config @@ -129,29 +205,57 @@ pub struct Args { } pub async fn execute_subcommand(command: Command) -> Result<()> { - match command { - Command::SendNativeTokenTransfer { - from, - nonce, - to, - amount, - } => { - let wallet_config = fetch_config()?; + let wallet_config = fetch_config()?; + let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config)?; + match command { + Command::SendNativeTokenTransfer { from, to, amount } => { let from = produce_account_addr_from_hex(from)?; let to = produce_account_addr_from_hex(to)?; - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config)?; - let res = wallet_core - .send_public_native_token_transfer(from, nonce, to, amount) + .send_public_native_token_transfer(from, to, amount) .await?; info!("Results of tx send is {res:#?}"); - //ToDo: Insert transaction polling logic here + let transfer_tx = wallet_core + .poll_public_native_token_transfer(res.tx_hash) + .await?; + + info!("Transaction data is {transfer_tx:?}"); + } + Command::RegisterAccount {} => { + let addr = wallet_core.create_new_account(); + + let key = wallet_core.storage.user_data.get_account_signing_key(&addr); + + info!("Generated new account with addr {addr:#?}"); + info!("With key {key:#?}"); + } + Command::FetchTx { tx_hash } => { + let tx_obj = wallet_core + .sequencer_client + .get_transaction_by_hash(tx_hash) + .await?; + + info!("Transaction object {tx_obj:#?}"); + } + Command::GetAccountBalance { addr } => { + let addr = Address::from_str(&addr)?; + + let balance = wallet_core.get_account_balance(addr).await?; + info!("Accounts {addr:#?} balance is {balance}"); + } + Command::GetAccountNonce { addr } => { + let addr = Address::from_str(&addr)?; + + let nonce = wallet_core.get_accounts_nonces(vec![addr]).await?[0]; + info!("Accounts {addr:#?} nonce is {nonce}"); } } + wallet_core.store_persistent_accounts()?; + Ok(()) } diff --git a/wallet/src/main.rs b/wallet/src/main.rs index f8c91f8..b296400 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,7 +1,7 @@ use anyhow::Result; use clap::Parser; use tokio::runtime::Builder; -use wallet::{execute_subcommand, Args}; +use wallet::{Args, execute_subcommand}; pub const NUM_THREADS: usize = 2; diff --git a/wallet/src/poller.rs b/wallet/src/poller.rs new file mode 100644 index 0000000..279197f --- /dev/null +++ b/wallet/src/poller.rs @@ -0,0 +1,68 @@ +use std::sync::Arc; + +use anyhow::Result; +use common::sequencer_client::SequencerClient; +use log::{info, warn}; + +use crate::config::WalletConfig; + +#[derive(Clone)] +///Helperstruct to poll transactions +pub struct TxPoller { + pub polling_max_blocks_to_query: usize, + pub polling_max_error_attempts: u64, + pub polling_error_delay_millis: u64, + pub polling_delay_millis: u64, + pub client: Arc, +} + +impl TxPoller { + pub fn new(config: WalletConfig, client: Arc) -> Self { + Self { + polling_delay_millis: config.seq_poll_timeout_millis, + polling_max_blocks_to_query: config.seq_poll_max_blocks, + polling_max_error_attempts: config.seq_poll_max_retries, + polling_error_delay_millis: config.seq_poll_retry_delay_millis, + client: client.clone(), + } + } + + pub async fn poll_tx(&self, tx_hash: String) -> Result { + let max_blocks_to_query = self.polling_max_blocks_to_query; + + info!("Starting poll for transaction {tx_hash:#?}"); + for poll_id in 1..max_blocks_to_query { + info!("Poll {poll_id}"); + + let mut try_error_counter = 0; + + let tx_obj = loop { + let tx_obj = self + .client + .get_transaction_by_hash(tx_hash.clone()) + .await + .inspect_err(|err| { + warn!("Failed to get transaction by hash {tx_hash:#?} with error: {err:#?}") + }); + + if let Ok(tx_obj) = tx_obj { + break tx_obj; + } else { + try_error_counter += 1; + } + + if try_error_counter > self.polling_max_error_attempts { + anyhow::bail!("Number of retries exceeded"); + } + }; + + if tx_obj.transaction.is_some() { + return Ok(tx_obj.transaction.unwrap()); + } + + tokio::time::sleep(std::time::Duration::from_millis(self.polling_delay_millis)).await; + } + + anyhow::bail!("Transaction not found in preconfigured amount of blocks"); + } +}