diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 09360657..a0d97d43 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -322,7 +322,7 @@ dependencies = [ "log", "parking", "polling", - "rustix 0.37.27", + "rustix 0.37.24", "slab", "socket2 0.4.9", "waker-fn", @@ -350,7 +350,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 0.37.27", + "rustix 0.37.24", "signal-hook", "windows-sys 0.48.0", ] @@ -420,8 +420,8 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "avail-common" -version = "0.2.0" -source = "git+https://github.com/availx/avail-lib?tag=v0.3.0#48e27c4f9d8ab69cfb2bdf65bf27b1ddc9b28fd4" +version = "0.5.0" +source = "git+https://github.com/availx/avail-lib?tag=v0.5.0#9db153348a031d5a94c674fa1187182b5d65ca4e" dependencies = [ "aes-gcm", "app_dirs2", @@ -442,7 +442,6 @@ dependencies = [ "serde", "serde_json", "snarkvm", - "tauri", "tokio", "tracing", "ureq", @@ -452,7 +451,7 @@ dependencies = [ [[package]] name = "avail_wallet" -version = "0.0.2" +version = "0.0.1" dependencies = [ "app_dirs2", "avail-common", @@ -490,6 +489,7 @@ dependencies = [ "ureq", "uuid", "whoami", + "zeroize", ] [[package]] @@ -2024,9 +2024,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -2034,7 +2034,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.0.0", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -3889,9 +3889,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.27" +version = "0.37.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +checksum = "4279d76516df406a8bd37e7dff53fd37d1a093f997a3c34a5c21658c126db06d" dependencies = [ "bitflags 1.3.2", "errno", @@ -7009,9 +7009,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 02b13b01..117e3ed3 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "avail_wallet" -version = "0.0.2" +version = "0.0.1" description = "A Tauri App" authors = ["Avail"] license = "" @@ -19,8 +19,8 @@ tauri-build = { version = "2.0.0-alpha.11", features = [] } [dependencies] app_dirs = { package = "app_dirs2", version = "2.5" } -avail-common = { git = "https://github.com/availx/avail-lib", tag = "v0.3.0", features = [ - "tauri", +avail-common = { git = "https://github.com/availx/avail-lib", tag = "v0.5.0", features = [ + "snarkvm", ] } bs58 = "0.5.0" chrono = "0.4.26" @@ -50,6 +50,11 @@ tokio = { version = "1.29.1", features = ["full"] } ureq = { version = "2.7.1", features = ["json"] } uuid = { version = "1.4.1", features = ["v4", "serde"] } whoami = "1.4.1" +zeroize = { version = "1.7.0", features = [ + "aarch64", + "zeroize_derive", + "alloc", +] } [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] keyring = "2.0.5" @@ -60,7 +65,6 @@ tid-rs = { git = "https://github.com/Zack-Xb/tid-rs" } [profile.release] -strip = true opt-level = 3 lto = "thin" incremental = true @@ -79,11 +83,6 @@ lto = "thin" incremental = true [profile.test] -opt-level = 2 -lto = "thin" -incremental = true -debug = true -debug-assertions = true [features] # this feature is used for production builds or when `devPath` points to the filesystem diff --git a/backend/icons/128x128.png b/backend/icons/128x128.png index b9ef63f7..8b525935 100644 Binary files a/backend/icons/128x128.png and b/backend/icons/128x128.png differ diff --git a/backend/icons/32x32.png b/backend/icons/32x32.png index 68f32dbf..92985971 100644 Binary files a/backend/icons/32x32.png and b/backend/icons/32x32.png differ diff --git a/backend/icons/icon.ico b/backend/icons/icon.ico index 3b71f77f..6c856dae 100644 Binary files a/backend/icons/icon.ico and b/backend/icons/icon.ico differ diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 00000000..dfb18f11 --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "backend", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/backend/src/api/aleo_client.rs b/backend/src/api/aleo_client.rs index 50c6484f..8940794f 100644 --- a/backend/src/api/aleo_client.rs +++ b/backend/src/api/aleo_client.rs @@ -23,12 +23,15 @@ pub fn setup_local_client() -> AleoAPIClient { } pub fn setup_client() -> AvailResult> { - //let network = get_network()?; + dotenv::dotenv().ok(); let node_api_obscura = match std::env::var("TESTNET_API_OBSCURA") { Ok(val) => val, Err(_e) => "".to_string(), }; + + println!("Node API Obscura: {:?}", node_api_obscura); + let base_url = format!( "https://aleo-testnet3.obscura.build/v1/{}", node_api_obscura @@ -134,7 +137,7 @@ pub static ALEO_CLIENT: Lazy>> = #[test] fn test_new_client() { - let api_client = setup_local_client::(); + let api_client = setup_client::().unwrap(); let height = api_client.latest_height().unwrap(); println!("Height: {:?}", height); diff --git a/backend/src/api/client.rs b/backend/src/api/client.rs index 3544b8f7..40e899b5 100644 --- a/backend/src/api/client.rs +++ b/backend/src/api/client.rs @@ -10,6 +10,10 @@ pub fn get_rm_client_with_session( method: reqwest::Method, path: &str, ) -> AvailResult { + dotenv::dotenv().ok(); + + let api = env!("API"); + let client = reqwest::Client::new(); let cookie_name = "id"; @@ -25,7 +29,8 @@ pub fn get_rm_client_with_session( }; let cookie_value = format!("{}={}", cookie_name, session); - let url = format!("http://{}:8001/{}", HOST, path); + let url = format!("{}/encrypted_data/{}", api, path); + let request = client .request(method, url) .header(reqwest::header::COOKIE, cookie_value); @@ -36,6 +41,10 @@ pub fn get_um_client_with_session( method: reqwest::Method, path: &str, ) -> AvailResult { + dotenv::dotenv().ok(); + + let api = env!("API"); + let client = reqwest::Client::new(); let cookie_name = "id"; @@ -52,8 +61,7 @@ pub fn get_um_client_with_session( let cookie_value = format!("{}={}", cookie_name, session); - let url = format!("http://{}:8000/{}", HOST, path); - println!("URL: {}", url); + let url = format!("{}/{}", api, path); let request = client .request(method, url) .header(reqwest::header::COOKIE, cookie_value); diff --git a/backend/src/api/encrypted_data.rs b/backend/src/api/encrypted_data.rs index 99ac47aa..df941b76 100644 --- a/backend/src/api/encrypted_data.rs +++ b/backend/src/api/encrypted_data.rs @@ -17,9 +17,10 @@ use avail_common::{ models::{ encrypted_data::{ Data, DataRequest, EncryptedData, EncryptedDataRecord, EncryptedDataSyncRequest, - EncryptedDataUpdateRequest, + EncryptedDataUpdateRequest,PageRequest }, traits::encryptable::EncryptedStruct, + }, }; @@ -27,44 +28,51 @@ use avail_common::{ /// update encrypted data by id pub async fn update_data(data: Vec, idx: Vec) -> AvailResult { - let request = data - .into_iter() - .enumerate() - .map(|(i, data)| { - let id = Uuid::from_str(&idx[i])?; - - Ok(EncryptedDataUpdateRequest { - id, - ciphertext: data.ciphertext, - nonce: data.nonce, - }) - }) - .collect::, AvError>>()? - .into_iter() - .collect_vec(); - - let res = get_rm_client_with_session(reqwest::Method::PUT, "data")? - .json(&request) - .send() - .await?; + + const MAX_BATCH_SIZE: usize = 300; - if res.status() == 200 { - let result = res.text().await?; + // Assuming data and idx have the same length + let batches = data.chunks(MAX_BATCH_SIZE); + let idx_batches = idx.chunks(MAX_BATCH_SIZE); - Ok(result) - } else if res.status() == 401 { - Err(AvailError::new( - AvailErrorType::Unauthorized, - "User session has expired.".to_string(), - "Your session has expired, please authenticate again.".to_string(), - )) - } else { - Err(AvailError::new( - AvailErrorType::External, - "Error updating encrypted data record ".to_string(), - "Error updating backup data.".to_string(), - )) + for (batch, idx_batch) in batches.zip(idx_batches) { + let request = batch + .iter() + .enumerate() + .map(|(i, data)| { + let id = Uuid::from_str(&idx_batch[i])?; + Ok(EncryptedDataUpdateRequest { + id, + ciphertext: data.ciphertext.clone(), + nonce: data.nonce.clone(), + }) + }) + .collect::, AvailError>>()?; + + let res = get_rm_client_with_session(reqwest::Method::PUT, "data")? + .json(&request) + .send() + .await?; + + if res.status() == 200 { + let _result = res.text().await?; + }else if res.status() == 401 { + return Err(AvailError::new( + AvailErrorType::Unauthorized, + "User session has expired.".to_string(), + "Your session has expired, please authenticate again.".to_string(), + )); + }else{ + return Err(AvailError::new( + AvailErrorType::External, + "Error updating encrypted data record ".to_string(), + "Error updating backup data.".to_string(), + )); + } + } + + Ok("Updated Succesfully".to_string()) } /// update transactions received to synced @@ -108,6 +116,7 @@ pub async fn get_new_transaction_messages( let result: Vec = res.json().await?; + println!("Enc Data {:?}",result); let encrypted_txs = result .clone() .into_iter() @@ -151,35 +160,43 @@ pub async fn get_new_transaction_messages( } pub async fn post_encrypted_data(request: Vec) -> AvailResult> { - let request = request - .into_iter() - .map(|data| EncryptedDataRecord::from(data.to_owned())) - .collect::>(); - - let res = get_rm_client_with_session(reqwest::Method::POST, "data")? - .json(&request) - .send() - .await?; + const MAX_BATCH_SIZE: usize = 300; + let mut ids: Vec = Vec::new(); - if res.status() == 200 { - let result = res.text().await?; + // Split the request into batches of MAX_BATCH_SIZE + let batches = request.chunks(MAX_BATCH_SIZE); - let ids: Vec = serde_json::from_str(&result).unwrap(); + for batch in batches { + let request = batch + .into_iter() + .map(|data| EncryptedDataRecord::from(data.to_owned())) + .collect::>(); - Ok(ids) - } else if res.status() == 401 { - Err(AvailError::new( - AvailErrorType::Unauthorized, - "User session has expired.".to_string(), - "Your session has expired, please authenticate again.".to_string(), - )) - } else { - Err(AvailError::new( - AvailErrorType::External, - "Error posting encrypted data ".to_string(), - "".to_string(), - )) + let res = get_rm_client_with_session(reqwest::Method::POST, "data")? + .json(&request) + .send() + .await?; + + if res.status() == 200 { + let result = res.text().await?; + let batch_ids: Vec = serde_json::from_str(&result)?; + ids.extend(batch_ids); + } else if res.status() == 401 { + return Err(AvailError::new( + AvailErrorType::Unauthorized, + "User session has expired.".to_string(), + "Your session has expired, please authenticate again.".to_string(), + )); + } else { + return Err(AvailError::new( + AvailErrorType::External, + "Error posting encrypted data ".to_string(), + "".to_string(), + )); + } } + + Ok(ids) } pub async fn send_transaction_in(request: EncryptedData) -> AvailResult { @@ -220,21 +237,21 @@ pub async fn delete_invalid_transactions_in(ids: Vec) -> AvailResult AvailResult { - let res = get_rm_client_with_session(reqwest::Method::GET, "recover_data")? +pub async fn get_data_count() -> AvailResult{ + let res = get_rm_client_with_session(reqwest::Method::GET, "data_count")? .send() .await?; if res.status() == 200 { - let result: Data = res.json().await?; - - Ok(result) + let result = res.text().await?; + let count = result.parse::()?; + Ok(count) } else if res.status() == 401 { Err(AvailError::new( AvailErrorType::Unauthorized, @@ -242,13 +259,69 @@ pub async fn recover_data(_address: &str) -> AvailResult { "Your session has expired, please authenticate again.".to_string(), )) } else { - println!("{:?}", res); Err(AvailError::new( AvailErrorType::External, - "Error recovering encrypted data ".to_string(), - "".to_string(), + "Error getting encrypted data count".to_string(), + "Error getting encrypted data count".to_string(), )) } + +} + +pub async fn recover_data(_address: &str) -> AvailResult { + let data_count = get_data_count().await?; + let pages = (data_count as f64 / 300.0).ceil() as i64; + + let mut encrypted_data: Vec = vec![]; + + for page in 0..pages { + + let page_request = PageRequest { + page + }; + + let res = get_rm_client_with_session(reqwest::Method::GET, "recover_data")? + .json(&page_request) + .send() + .await?; + + if res.status() == 200 { + let result: Data = res.json().await?; + encrypted_data.push(result); + } else if res.status() == 401 { + return Err(AvailError::new( + AvailErrorType::Unauthorized, + "User session has expired.".to_string(), + "Your session has expired, please authenticate again.".to_string(), + )); + } else { + return Err(AvailError::new( + AvailErrorType::External, + "Error recovering encrypted data ".to_string(), + "Error recovering encrypted data".to_string(), + )); + } + } + + let mut record_pointers: Vec = vec![]; + let mut transactions: Vec = vec![]; + let mut transitions: Vec = vec![]; + let mut deployments: Vec = vec![]; + + for data in encrypted_data { + record_pointers.extend(data.record_pointers); + transactions.extend(data.transactions); + transitions.extend(data.transitions); + deployments.extend(data.deployments); + } + + Ok(Data { + record_pointers, + transactions, + transitions, + deployments, + }) + } pub async fn delete_all_server_storage() -> AvailResult { @@ -276,27 +349,105 @@ pub async fn delete_all_server_storage() -> AvailResult { } pub async fn import_encrypted_data(request: DataRequest) -> AvailResult { - let res = get_rm_client_with_session(reqwest::Method::POST, "import_data")? - .json(&request) - .send() - .await?; - if res.status() == 200 { - let result = res.text().await?; - Ok(result) - } else if res.status() == 401 { - Err(AvailError::new( - AvailErrorType::Unauthorized, - "User session has expired.".to_string(), - "Your session has expired, please authenticate again.".to_string(), - )) + // check that every vector in data request does not exceed 75 + + let record_pointers = request.data.record_pointers.len(); + let transactions = request.data.transactions.len(); + let transitions = request.data.transitions.len(); + let deployments = request.data.deployments.len(); + + // if they exceed 300 in sum, then split into several DataRequest where the sum of the data vectors inside is less than 300 + if record_pointers + transactions + transitions + deployments > 300 { + + let mut data_requests: Vec = vec![]; + + let mut record_pointers = request.data.record_pointers.clone(); + let mut transactions = request.data.transactions.clone(); + let mut transitions = request.data.transitions.clone(); + let mut deployments = request.data.deployments.clone(); + + while record_pointers.len() + transactions.len() + transitions.len() + deployments.len() > 300 { + let mut record_pointers_batch = record_pointers.split_off(75); + let mut transactions_batch = transactions.split_off(75); + let mut transitions_batch = transitions.split_off(75); + let mut deployments_batch = deployments.split_off(75); + + let data: Data = Data { + record_pointers: record_pointers.clone(), + transactions: transactions.clone(), + transitions: transitions.clone(), + deployments: deployments.clone(), + }; + + let data_request = DataRequest { + address: request.address.clone(), + data, + }; + + data_requests.push(data_request); + + record_pointers = record_pointers_batch; + transactions = transactions_batch; + transitions = transitions_batch; + deployments = deployments_batch; + } + + let data = Data { + record_pointers, + transactions, + transitions, + deployments, + }; + + let data_request = DataRequest { + address: request.address.clone(), + data, + }; + + data_requests.push(data_request); + + for data_request in data_requests { + let res = get_rm_client_with_session(reqwest::Method::POST, "import_data")? + .json(&data_request) + .send() + .await?; + + if res.status() != 200 { + return Err(AvailError::new( + AvailErrorType::External, + "Error importing encrypted data.".to_string(), + "Error backing up encrypted data.".to_string(), + )); + } + } + + Ok("Imported Succesfully".to_string()) } else { - Err(AvailError::new( - AvailErrorType::External, - "Error importing encrypted data.".to_string(), - "Error backing up encrypted data.".to_string(), - )) + let res = get_rm_client_with_session(reqwest::Method::POST, "import_data")? + .json(&request) + .send() + .await?; + + if res.status() == 200 { + let result = res.text().await?; + Ok(result) + } else if res.status() == 401 { + Err(AvailError::new( + AvailErrorType::Unauthorized, + "User session has expired.".to_string(), + "Your session has expired, please authenticate again.".to_string(), + )) + } else { + Err(AvailError::new( + AvailErrorType::External, + "Error importing encrypted data.".to_string(), + "Error backing up encrypted data.".to_string(), + )) + } + } + } #[cfg(test)] diff --git a/backend/src/api/fee.rs b/backend/src/api/fee.rs index 4d51e5f0..9756042a 100644 --- a/backend/src/api/fee.rs +++ b/backend/src/api/fee.rs @@ -120,7 +120,7 @@ mod tests { async fn get_execution_object() -> AvailResult> { let pk = PrivateKey::::from_str(TESTNET3_PRIVATE_KEY).unwrap(); - let api_client = setup_local_client::(); + let api_client = setup_client::().unwrap(); let recipient = Address::::from_str(TESTNET3_ADDRESS).unwrap(); let program = api_client.get_program("credits.aleo").unwrap(); @@ -149,7 +149,7 @@ mod tests { #[tokio::test] async fn test_get_execution_object() { let pk = PrivateKey::::from_str(TESTNET3_PRIVATE_KEY).unwrap(); - let api_client = setup_local_client::(); + let api_client = setup_client::().unwrap(); let recipient = Address::::from_str(TESTNET3_ADDRESS).unwrap(); let program = api_client.get_program("credits.aleo").unwrap(); diff --git a/backend/src/api/user.rs b/backend/src/api/user.rs index a215ac6d..999ed30f 100644 --- a/backend/src/api/user.rs +++ b/backend/src/api/user.rs @@ -23,10 +23,14 @@ use avail_common::{ // create user online account pub async fn create_user(request: User) -> AvailResult { + dotenv::dotenv().ok(); + + let api = env!("API"); + let client = reqwest::Client::new(); let res = client - .post(format!("http://{}:8000/user", HOST)) + .post(format!("{}/user",api)) .json(&request) .send() .await?; diff --git a/backend/src/lib.rs b/backend/src/lib.rs index a0e0117b..92d05ccf 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -1,13 +1,14 @@ -mod api; -mod helpers; -mod models; -mod services; +pub mod api; +pub mod helpers; +pub mod models; +pub mod services; use tauri::Manager; use services::account::generation::create_seed_phrase_wallet; use services::account::generation::import_wallet; use services::account::phrase_recovery::recover_wallet_from_seed_phrase; +use services::account::utils::open_url; use services::authentication::session::get_session; use services::local_storage::persistent_storage::{ get_address_string, get_auth_type, get_backup_flag, get_language, get_last_sync, get_network, @@ -20,19 +21,21 @@ use services::account::key_management::ios::prepare_context; use api::user::{update_backup_flag, update_username}; use services::local_storage::{ encrypted_data::get_and_store_all_data, - utils::{delete_util, get_private_key_tauri, get_seed_phrase, get_view_key_tauri}, + tokens::get_stored_tokens, + utils::{delete_util, get_private_key_tauri, get_seed_phrase, get_view_key_tauri,delete_local_for_recovery}, }; // record handliong services +// use crate::services::record_handling::utils::get_all_nft_data; use services::record_handling::{ sync::{blocks_sync, sync_backup, txs_sync}, transfer::{pre_install_inclusion_prover, transfer}, }; - // wallet connect services use crate::services::wallet_connect_api::{ decrypt_records, get_avail_event, get_avail_events, get_balance, get_event, get_events, get_records, get_succinct_avail_event, get_succinct_avail_events, request_create_event, sign, + verify, }; #[cfg_attr(mobile, tauri::mobile_entry_point)] @@ -59,6 +62,7 @@ pub fn run() { import_wallet, get_username, delete_util, + delete_local_for_recovery, get_private_key_tauri, get_view_key_tauri, get_seed_phrase, @@ -70,6 +74,8 @@ pub fn run() { get_network, get_language, update_language, + get_stored_tokens, + open_url, /* Authentication */ get_session, get_auth_type, @@ -81,6 +87,7 @@ pub fn run() { /* Avail Services */ get_avail_event, get_avail_events, + // get_all_nft_data, transfer, /* --Wallet Connect Api */ get_event, @@ -92,6 +99,7 @@ pub fn run() { get_balance, get_succinct_avail_event, get_succinct_avail_events, + verify, /* Aleo Helpers */ pre_install_inclusion_prover ]) @@ -121,6 +129,7 @@ pub fn run() { update_username, get_username, delete_util, + delete_local_for_recovery, get_private_key_tauri, get_view_key_tauri, get_seed_phrase, @@ -133,6 +142,8 @@ pub fn run() { get_network, get_language, update_language, + get_stored_tokens, + open_url, /* Authentication */ get_session, get_auth_type, @@ -154,6 +165,7 @@ pub fn run() { get_balance, get_succinct_avail_event, get_succinct_avail_events, + verify, /* Aleo Helpers */ pre_install_inclusion_prover ]) @@ -183,6 +195,7 @@ pub fn run() { update_username, get_username, delete_util, + delete_local_for_recovery, get_private_key_tauri, get_view_key_tauri, get_seed_phrase, @@ -195,6 +208,8 @@ pub fn run() { get_network, get_language, update_language, + get_stored_tokens, + open_url, /* Authentication */ get_session, get_auth_type, @@ -216,6 +231,7 @@ pub fn run() { get_balance, get_succinct_avail_event, get_succinct_avail_events, + verify, /* Aleo Helpers */ pre_install_inclusion_prover ]) diff --git a/backend/src/models/pointers/deployment.rs b/backend/src/models/pointers/deployment.rs index 95ea26db..43de76e1 100644 --- a/backend/src/models/pointers/deployment.rs +++ b/backend/src/models/pointers/deployment.rs @@ -33,6 +33,7 @@ pub struct DeploymentPointer { pub fee: f64, pub state: TransactionState, pub block_height: Option, + pub spent_fee_nonce: Option, pub created: DateTime, pub finalized: Option>, pub error: Option, @@ -59,6 +60,7 @@ impl DeploymentPointer { fee: f64, state: TransactionState, block_height: Option, + spent_fee_nonce: Option, created: DateTime, finalized: Option>, error: Option, @@ -69,6 +71,7 @@ impl DeploymentPointer { fee, state, block_height, + spent_fee_nonce, created, finalized, error, diff --git a/backend/src/models/pointers/message.rs b/backend/src/models/pointers/message.rs index 67996864..5cbb6560 100644 --- a/backend/src/models/pointers/message.rs +++ b/backend/src/models/pointers/message.rs @@ -16,6 +16,8 @@ use avail_common::{ }, }; +use crate::api::aleo_client::setup_client; + /// Encrypted and sent to the address the wallet owner interacted with in the transaction to avoid scanning times #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct TransactionMessage { @@ -89,21 +91,7 @@ impl TransactionMessage { /// Checks if the transaction has been stored before and checks if the transaction is found at the confirmed block height pub fn verify(&self) -> AvailResult<(Option>, DateTime)> { - /* - let node_api_obscura = match std::env::var("TESTNET_API_OBSCURA") { - Ok(val) => val, - Err(_e) => "".to_string(), - }; - let base_url = format!("https://aleo-testnet3.obscura.build/v1/{}", node_api_obscura); - let api_client = AleoAPIClient::::new(&base_url, network_str)?; - */ - - let dev_node_ip = match std::env::var("DEV_NODE_IP") { - Ok(val) => val, - Err(_e) => "".to_string(), - }; - - let api_client = AleoAPIClient::::local_testnet3("3030", &dev_node_ip); + let api_client = setup_client::()?; let block = api_client.get_block(self.confirmed_height)?; let timestamp = get_timestamp_from_i64(block.timestamp())?; diff --git a/backend/src/models/pointers/record.rs b/backend/src/models/pointers/record.rs index 8df949f5..9334fd45 100644 --- a/backend/src/models/pointers/record.rs +++ b/backend/src/models/pointers/record.rs @@ -14,7 +14,8 @@ use avail_common::{ }, }; -use crate::api::aleo_client::setup_local_client; +use crate::api::aleo_client::setup_client; + use crate::services::{ local_storage::{ persistent_storage::{get_address, get_network}, @@ -133,7 +134,7 @@ impl AvailRecord { } pub fn to_record(&self) -> AvailResult>> { - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let record_transaction = api_client.get_transaction(self.pointer.transaction_id)?; @@ -157,7 +158,7 @@ impl AvailRecord { pub fn to_record_texts_and_data( &self, ) -> AvailResult<(String, String, HashMap)> { - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let record_transaction = api_client.get_transaction(self.pointer.transaction_id)?; diff --git a/backend/src/models/pointers/transaction.rs b/backend/src/models/pointers/transaction.rs index 2c7b2dfa..595dd704 100644 --- a/backend/src/models/pointers/transaction.rs +++ b/backend/src/models/pointers/transaction.rs @@ -10,7 +10,7 @@ use crate::models::event::{ }; use crate::{ - api::aleo_client::setup_local_client, + api::aleo_client::setup_client, services::local_storage::{ encrypted_data::store_encrypted_data, persistent_storage::{get_address, get_address_string, get_network}, @@ -41,6 +41,7 @@ pub struct TransactionPointer { executed_program_id: Option, executed_function_id: Option, transitions: Vec>, + spent_record_pointers_nonces: Vec, created: DateTime, finalized: Option>, message: Option, @@ -67,6 +68,7 @@ impl TransactionPointer { executed_program_id: Option, executed_function_id: Option, transitions: Vec>, + spent_record_pointers_nonces: Vec, created: DateTime, finalized: Option>, message: Option, @@ -83,6 +85,7 @@ impl TransactionPointer { executed_program_id, executed_function_id, transitions, + spent_record_pointers_nonces, created, finalized, message, @@ -127,6 +130,11 @@ impl TransactionPointer { self.transitions.clone() } + #[allow(dead_code)] + pub fn spent_record_pointers_nonces(&self) -> Vec { + self.spent_record_pointers_nonces.clone() + } + pub fn created(&self) -> DateTime { self.created } @@ -305,7 +313,7 @@ impl TransactionPointer { let v_key = VIEWSESSION.get_instance::()?; - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let event_transaction = match self.transaction_id { Some(id) => match api_client.get_transaction(id) { @@ -409,7 +417,7 @@ impl TransactionPointer { let v_key = VIEWSESSION.get_instance::()?; - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let event_transaction = match self.transaction_id { Some(id) => match api_client.get_transaction(id) { diff --git a/backend/src/models/pointers/transition.rs b/backend/src/models/pointers/transition.rs index 0fb212d8..9c482570 100644 --- a/backend/src/models/pointers/transition.rs +++ b/backend/src/models/pointers/transition.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use snarkvm::prelude::{Address, Network}; use uuid::Uuid; -use crate::api::aleo_client::setup_local_client; +use crate::api::aleo_client::setup_client; use crate::models::event::{ AvailEvent, Event, EventTransition, Network as EventNetwork, SuccinctAvailEvent, Visibility, }; @@ -129,7 +129,7 @@ impl TransitionPointer { } }; - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let transaction = api_client.get_transaction(self.transaction_id)?; let transition = match transaction.find_transition(&self.id) { @@ -195,7 +195,7 @@ impl TransitionPointer { } }; - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let transaction = api_client.get_transaction(self.transaction_id)?; diff --git a/backend/src/models/wallet.rs b/backend/src/models/wallet.rs index 6d4200c8..79ce1f6e 100644 --- a/backend/src/models/wallet.rs +++ b/backend/src/models/wallet.rs @@ -1,172 +1,269 @@ -#![allow(dead_code)] - -use bip39::{Language, Mnemonic}; -use serde::{ser::SerializeStruct, Serialize}; +use bip39::{Mnemonic, MnemonicType, Seed}; use snarkvm::{ - console::{network::Environment, prelude::*}, - prelude::{Address, PrivateKey, Testnet3, ViewKey}, + console::prelude::*, + prelude::{Address, PrivateKey, ViewKey}, }; -use avail_common::errors::{AvError, AvailError, AvailErrorType, AvailResult}; +use avail_common::errors::{AvailError, AvailResult}; +use zeroize::Zeroize; -pub struct AvailWallet { - pub private_key: PrivateKey, - pub view_key: ViewKey, +use crate::models::storage::languages::Languages; + +#[derive(Debug)] +pub struct BetterAvailWallet { pub address: Address, + pub view_key: ViewKey, + pub private_key: PrivateKey, + pub mnemonic: Option, } -impl AvailWallet { - ///Generates a new wallet - pub fn new() -> Result { - let private_key = PrivateKey::::new(&mut rand::thread_rng())?; +impl PartialEq for BetterAvailWallet { + fn eq(&self, other: &Self) -> bool { + self.private_key == other.private_key + && self.address == other.address + && self.view_key == other.view_key + } +} - let view_key = ViewKey::::try_from(&private_key)?; - let address = Address::::try_from(&private_key)?; +impl Eq for BetterAvailWallet {} - Ok(AvailWallet:: { - private_key, - view_key, - address, - }) +impl BetterAvailWallet { + /// Generates a new [`BetterAvailWallet`], whilst throwing an error if the seed phrase length is not 12, 15, 18, 21, or 24. + /// + /// ``` + /// use availx_lib::models::wallet::BetterAvailWallet; + /// # use availx_lib::models::storage::languages::Languages; + /// # use snarkvm::prelude::Testnet3; + /// + /// let wallet = BetterAvailWallet::::new(24, &Languages::English); + /// + /// assert!(wallet.is_ok()); + /// ``` + pub fn new(seed_phrase_len: usize, seed_lang: &Languages) -> AvailResult { + let mnemonic = Mnemonic::new( + MnemonicType::for_word_count(seed_phrase_len)?, + Languages::to_bip39_language(seed_lang), + ); + + // NOTE: EMPTY BECAUSE WE ARE NOT USING PASSWORDS FOR SPs + let seed = Seed::new(&mnemonic, ""); + + Self::from_mnemonic_seed(seed, mnemonic) } - ///Generates a wallet from the byte representation of a private key - pub fn from_bytes(bytes: &[u8]) -> Result { - let seed: [u8; 32] = bytes.try_into().map_err(|_| { - AvailError::new( - AvailErrorType::InvalidData, - "Error generating seed phrase".to_string(), - "Error generating seed phrase".to_string(), - ) - })?; + /// This method returns the bytes of the [`Field`] used to derive an Aleo [(docs)](https://developer.aleo.org/concepts/accounts#create-an-account) [`PrivateKey`]. + /// + /// Not to be confused with [`Seed`]. + pub fn get_seed_bytes(&self) -> AvailResult> { + let seed_bytes = self.private_key.to_bytes_le()?; + + Ok(seed_bytes) + } - let field = ::Field::from_bytes_le_mod_order(&seed); + /// Generates an [`AvailWallet`] from an arbitrary seed phrase, using the specified [`Language`]. + pub fn from_seed_phrase(seed_phrase: &str, lang: bip39::Language) -> AvailResult { + let mnemonic = Mnemonic::from_phrase(seed_phrase, lang)?; + let seed = Seed::new(&mnemonic, ""); + + Self::from_mnemonic_seed(seed, mnemonic) + } + + /// Generates a [`BetterAvailWallet`] from the [`Seed`] of a [`Mnemonic`]. + pub fn from_mnemonic_seed(seed: Seed, mnemonic: Mnemonic) -> AvailResult { + let bytes = &mut seed.as_bytes()[0..=32].to_vec(); + + let field = ::Field::from_bytes_le_mod_order(bytes); - //handle errors let private_key = PrivateKey::::try_from(FromBytes::read_le(&*field.to_bytes_le().unwrap()).unwrap())?; + bytes.zeroize(); + let view_key = ViewKey::::try_from(&private_key)?; let address = Address::::try_from(&private_key)?; - let seed_wallet = AvailWallet:: { - private_key, - view_key, + Ok(BetterAvailWallet:: { address, - }; + view_key, + private_key, + mnemonic: Some(mnemonic), + }) + } - Ok(seed_wallet) + /// Gets the private key string of an avail wallet. + pub fn get_private_key(&self) -> String { + self.private_key.to_string() + } + + /// Gets the view key string of an avail wallet. + pub fn get_view_key(&self) -> String { + self.view_key.to_string() } -} -///This is to be removed as will not be sent out to frontend using IPC -impl Serialize for AvailWallet { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_struct("AvailWallet", 3)?; - state.serialize_field("private_key", &self.private_key.to_string())?; - state.serialize_field("view_key", &self.view_key.to_string())?; - state.serialize_field("address", &self.address.to_string())?; - state.end() + /// Gets the address string of an avail wallet. + pub fn get_address(&self) -> String { + self.address.to_string() + } + + pub fn get_network(&self) -> String { + N::NAME.to_string() } } -pub struct AvailSeedWallet { - pub seed_phrase: String, - pub private_key: PrivateKey, - pub view_key: ViewKey, - pub address: Address, +/// Implementing the `Display` trait for the `BetterAvailWallet` struct. +impl Display for BetterAvailWallet { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.private_key) + } } -impl AvailSeedWallet { - ///Generates a new wallet with seed phrase - pub fn new(entropy: &[u8], seed_phrase: String) -> Result { - let field = ::Field::from_bytes_le_mod_order(entropy); - let binding = field.to_bytes_le()?; - let private_key = PrivateKey::::try_from(FromBytes::read_le(binding.as_slice())?)?; +/// Implementing the `TryFrom` trait for the `BetterAvailWallet` struct. +impl TryFrom for BetterAvailWallet { + type Error = AvailError; + fn try_from(value: String) -> AvailResult { + let private_key = PrivateKey::::from_str(&value)?; let view_key = ViewKey::::try_from(&private_key)?; let address = Address::::try_from(&private_key)?; - let seed_wallet = AvailSeedWallet:: { - seed_phrase, + + Ok(BetterAvailWallet:: { + address, + view_key, private_key, + mnemonic: None, + }) + } +} + +impl TryFrom<&str> for BetterAvailWallet { + type Error = AvailError; + + fn try_from(value: &str) -> AvailResult { + let private_key = PrivateKey::::from_str(value)?; + let view_key = ViewKey::::try_from(&private_key)?; + let address = Address::::try_from(&private_key)?; + + Ok(BetterAvailWallet:: { + address, view_key, + private_key, + mnemonic: None, + }) + } +} + +impl TryFrom> for BetterAvailWallet { + type Error = AvailError; + + fn try_from(value: PrivateKey) -> AvailResult { + let view_key = ViewKey::::try_from(&value)?; + let address = Address::::try_from(&value)?; + + Ok(BetterAvailWallet:: { address, - }; - Ok(seed_wallet) + view_key, + private_key: value, + mnemonic: None, + }) } +} - /// Generates a seed phrase wallet from the byte representation of a private key - pub fn from_bytes(seed: &[u8]) -> AvailResult { - let mnemonic = Mnemonic::from_entropy(seed, Language::English)?; +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + use snarkvm::prelude::Testnet3; - let seed: [u8; 32] = seed.try_into().map_err(|_| { - AvailError::new( - AvailErrorType::InvalidData, - "Error generating seed phrase".to_string(), - "Error generating seed phrase".to_string(), - ) - })?; + const PRIVATE_KEY: &str = "APrivateKey1zkpDqSfXcDcHdsvjkQhzF4NHTPPC63CBRHyaarTP3NAcHvg"; + const PHRASE: &str = "brave pass marine truly lecture fancy rail exotic destroy health always thunder wife decide situate index secret enter cruise prosper pudding about barely quit"; - let field = ::Field::from_bytes_le_mod_order(&seed); - let binding = field.to_bytes_le()?; - let field_bytes = binding.as_slice(); + #[rstest] + fn test_create_random_avail_wallet(#[values(12, 15, 18, 21, 24)] seed_phrase_len: usize) { + let wallet = BetterAvailWallet::::new(seed_phrase_len, &Languages::English); - let private_key = PrivateKey::::try_from(FromBytes::read_le(field_bytes)?)?; + assert!(wallet.is_ok()); - let view_key = ViewKey::::try_from(&private_key)?; - let address = Address::::try_from(&private_key)?; + let wallet = wallet.unwrap(); + println!("Wallet: {}", wallet.get_private_key()); + } - let seed_wallet = AvailSeedWallet:: { - seed_phrase: mnemonic.phrase().to_string(), - private_key, - view_key, - address, - }; + #[rstest] + fn test_from_seed_bytes(#[values(12, 15, 18, 21, 24)] seed_phrase_len: usize) { + let mnemonic = Mnemonic::new( + MnemonicType::for_word_count(seed_phrase_len).unwrap(), + Languages::to_bip39_language(&Languages::English), + ); + + assert_eq!(mnemonic.phrase().split(" ").count(), seed_phrase_len); + + let seed = Seed::new(&mnemonic, ""); + let bytes = &mut seed.as_bytes()[0..32].to_vec(); - Ok(seed_wallet) + assert_eq!(bytes.len(), 32); + + let wallet = BetterAvailWallet::::from_mnemonic_seed(seed, mnemonic); + + assert!(wallet.is_ok()); } - ///Generates a seed phrase wallet from the seed phrase - pub fn from_seed_phrase(seed_phrase: String) -> AvailResult { - let mnemonic = Mnemonic::from_phrase(&seed_phrase, Language::English)?; + #[rstest] + /// Test that a wallet can be created from the seed phrase. + fn test_get_seed_bytes(#[values(12, 15, 18, 21, 24)] seed_phrase_len: usize) { + let wallet = + BetterAvailWallet::::new(seed_phrase_len, &Languages::English).unwrap(); + let seed_bytes = wallet.get_seed_bytes().unwrap(); - let field = ::Field::from_bytes_le_mod_order(mnemonic.entropy()); - let binding = field.to_bytes_le()?; - let private_key = PrivateKey::::try_from(FromBytes::read_le(binding.as_slice())?)?; + assert_eq!(seed_bytes.len(), 32); + } - let view_key = ViewKey::::try_from(&private_key)?; - let address = Address::::try_from(&private_key)?; + #[rstest] + /// Test that an avail wallet can be created from the seed phrase. + fn test_from_seed_phrase() { + let mnemonic = Mnemonic::from_phrase(PHRASE, bip39::Language::English).unwrap(); - let seed_wallet = AvailSeedWallet:: { - seed_phrase: mnemonic.phrase().to_string(), - private_key, - view_key, - address, - }; + let seed_phrase = mnemonic.phrase(); + + let wallet = BetterAvailWallet::::from_seed_phrase( + seed_phrase, + Languages::to_bip39_language(&Languages::English), + ); + + assert!(wallet.is_ok()); - Ok(seed_wallet) + let wallet = wallet.unwrap(); + + assert_eq!(wallet.get_private_key(), PRIVATE_KEY) } -} -///This is to be removed as will not be sent out to frontend using IPC -impl Serialize for AvailSeedWallet { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_struct("AvailSeedWallet", 4)?; - state.serialize_field("seed_phrase", &self.seed_phrase)?; - state.serialize_field("private_key", &self.private_key.to_string())?; - state.serialize_field("view_key", &self.view_key.to_string())?; - state.serialize_field("address", &self.address.to_string())?; - state.end() + #[rstest] + /// Test that the private key string can be retrieved from the avail wallet. + fn test_get_private_key() { + let wallet = BetterAvailWallet::::try_from(PRIVATE_KEY).unwrap(); + let private_key = wallet.get_private_key(); + + assert_eq!(private_key, PRIVATE_KEY); } -} -pub struct SeedResult { - pub entropy: Vec, - pub seed_phrase: String, + #[rstest] + /// Test that the address string can be retrieved from the avail wallet. + fn test_get_view_key() { + let wallet = BetterAvailWallet::::try_from(PRIVATE_KEY).unwrap(); + let view_key = wallet.get_view_key(); + + assert_eq!( + view_key, + "AViewKey1icabKrKXiTjKnk1fd2p8NZ9etV8KZKNrejtiaHnav34N" + ); + } + + #[rstest] + fn test_get_address() { + let wallet = BetterAvailWallet::::try_from(PRIVATE_KEY).unwrap(); + let address = wallet.get_address(); + + assert_eq!( + address, + "aleo1lavuvpvklv3fdjwesr4pp5wekq2gjahu00krprnx8c2wc5xepuyqv64xk8" + ) + } } diff --git a/backend/src/models/wallet_connect/balance.rs b/backend/src/models/wallet_connect/balance.rs index ff30e136..a244e4fe 100644 --- a/backend/src/models/wallet_connect/balance.rs +++ b/backend/src/models/wallet_connect/balance.rs @@ -52,6 +52,7 @@ impl BalanceResponse { #[derive(Serialize, Deserialize, Debug)] pub struct BalanceRequest { + #[serde(rename = "assetId")] asset_id: Option, address: Option, } diff --git a/backend/src/models/wallet_connect/get_event.rs b/backend/src/models/wallet_connect/get_event.rs index ade8e8a3..6d5810c5 100644 --- a/backend/src/models/wallet_connect/get_event.rs +++ b/backend/src/models/wallet_connect/get_event.rs @@ -48,8 +48,8 @@ pub struct GetEventsRequest { impl GetEventsRequest { pub fn default() -> Self { Self { - filter: None, - page: None, + filter: Some(EventsFilter::default()), + page: Some(0), } } } @@ -64,6 +64,28 @@ pub struct EventsFilter { pub function_id: Option, } +impl EventsFilter { + pub fn new( + event_type: Option, + program_id: Option, + function_id: Option, + ) -> Self { + Self { + event_type, + program_id, + function_id, + } + } + + pub fn default() -> Self { + Self { + event_type: None, + program_id: None, + function_id: None, + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetEventsResponse { pub events: Vec, diff --git a/backend/src/services.rs b/backend/src/services.rs index 2557d129..05b0ad84 100644 --- a/backend/src/services.rs +++ b/backend/src/services.rs @@ -2,4 +2,5 @@ pub mod account; pub mod authentication; pub mod local_storage; pub mod record_handling; +pub mod records; pub mod wallet_connect_api; diff --git a/backend/src/services/README.md b/backend/src/services/README.md new file mode 100644 index 00000000..06762268 --- /dev/null +++ b/backend/src/services/README.md @@ -0,0 +1,43 @@ +# Services Documentation + +## Overview + +This is the logic behind Avail wallet, this encapsulates account creation, handling of transactions and records, wallet recovery using shamir's secret sharing scheme and storing on the android/ios secure enclaves. + +## Services + +### generation.rs + +This is the initial generation of a wallet for a user. The wallet can either be without a seed phrase or with a seed phrase, if they have a seed phrase then they dont't make use of our recovery system. + +The user inputs a username and passoword and chooses if they would like to authenticate using biometrics. Once inputted an aleo keypair is generated and the local storage process starts, along with the sharding as preperation in case of recovery. The user can also choose to allow others to reference them by their username and in that case we store the username and address in Avail's database. + +### local_storage + +#### iOS + + For iOS we use the iOS keychain, a secure data storage system made by apple, to store the private key and this is protected either by the application password or biometrics. The private key is encrypted by an aes-key generated and stored in the keychain's secure enclave and this is non extractable. + + We use apple's `security-framework` and `local-authentication-framework` to access the keychain and authnetication functionalities. + +#### Android + +For android we use the android keystore, a secure data storage system made by google. This generates and stores a cryptographic key AES | RSA in a TEE (Trusted Execution Environment) or secure enclave to protect it from extraction. The key is protected by biometrics. Then we encrypt the private key and viewing key using the key in the keystore and store it in the android shared preferences. + +In the case of authenticating the user with application password, we hash the password using argon2, generate an aes key from the derived password hash and use that to encrypt a randomly generated aes-key. With the randomly generated aes-key we encrypt the private key and viewing key and store it an sqlite database embeded in the user's application data. The password hash is not stored locally and must be inputted by the user to authenticate and use the keys. + +### recovery.rs + +This process is only available to those who opt in and do kyc verification so we can prove their identity when they want to recover their wallet. + +Using Shamir's secret sharing scheme we split the private key into 3 encrypted shards and these are sent to three entities. These entities are the user's cloud, either iCloud keychain or Gdrive, Avail's secure data storage and our partner's secure data storage. Shamir is set in a way that 2 of the 3 shards is sufficient to reconstruct the original private key. + +By default the user's cloud and Avail's secure data storage is used for recovery. When a user wants to recover they do kyc verification and if verified the shard is sent from Avail to the user. Then the shards are reconstructed and the private key is stored locally on the user's device as explained in the local_storage section. + +If the shard is not present in the user's specified cloud then we must make use of the partner's secure data storage. Again in this case the user must do kyc verification and input their email address and if verified then recovery will start. We make it clear that in our system only one encrypted shard is ever being transferred using https. Therefore the user will receive two random emails along the following hours and each email will take them to the Avail app and initiate the recovery of a shard in that instance. This is done for security purposes, once both shards are recovered the private key is reconstructed and stored locally on the user's device as explained in the local_storage section. + +We will move research and develop a solution that stills verify the user without kyc as we move forward and innovate in the future. + +### records.rs + +This is the logic behind handling the records of the user locally i.e getting the user's balance and past transaction and filtering the transactions. Also this includes the transfer function allowing to send tokens to someone else on the aleo network.This is done using the aleo rust sdk. diff --git a/backend/src/services/account.rs b/backend/src/services/account.rs index 85063adc..40910d35 100644 --- a/backend/src/services/account.rs +++ b/backend/src/services/account.rs @@ -1,5 +1,4 @@ pub mod generation; pub mod key_management; pub mod phrase_recovery; -pub mod shard_recovery; pub mod utils; diff --git a/backend/src/services/account/generation.rs b/backend/src/services/account/generation.rs index 6d335980..2aaff552 100644 --- a/backend/src/services/account/generation.rs +++ b/backend/src/services/account/generation.rs @@ -1,25 +1,18 @@ -use bip39::{Language, Mnemonic}; -use rand::{rngs::StdRng, Rng, SeedableRng}; -use snarkvm::{ - console::prelude::*, - prelude::{Network, PrivateKey, Testnet3, ViewKey}, -}; -use std::str::FromStr; +use snarkvm::{console::prelude::*, prelude::Testnet3}; -use crate::api::user::create_user; -use crate::models::{ - storage::languages::Languages, - wallet::{AvailSeedWallet, AvailWallet, SeedResult}, -}; +use crate::models::storage::languages::Languages; use crate::services::account::{ key_management::key_controller::KeyController, utils::generate_discriminant, }; +use crate::services::authentication::session::get_session_after_creation; +use crate::services::local_storage::persistent_storage::get_language; use crate::services::local_storage::{ encrypted_data::{get_and_store_all_data, initialize_encrypted_data_table}, persistent_storage::initial_user_preferences, session::{password::PASS, view::VIEWSESSION}, tokens::init_tokens_table, }; +use crate::{api::user::create_user, models::wallet::BetterAvailWallet}; #[cfg(target_os = "linux")] use crate::services::account::key_management::key_controller::linuxKeyController; @@ -32,64 +25,6 @@ use crate::services::account::key_management::key_controller::macKeyController; use avail_common::{errors::AvailResult, models::user::User}; -use super::super::authentication::session::get_session_after_creation; - -/// Generates a 12 word seed phrase using bip39 crate -fn generate_seed_phrase() -> AvailResult { - let mut rng = StdRng::from_entropy(); - let mut seed = [0u8; 16]; - - rng.fill(&mut seed); - - let mnemonic = Mnemonic::from_entropy(&seed, Language::English)?; - - let phrase: &str = mnemonic.phrase(); - - let seed_res = SeedResult { - entropy: mnemonic.entropy().to_vec(), - seed_phrase: phrase.to_string(), - }; - - Ok(seed_res) -} - -/// Generates a 24 word seed phrase using bip39 crate -fn generate_long_seed_phrase() -> AvailResult { - let mut rng = StdRng::from_entropy(); - let mut seed = [0u8; 32]; - - rng.fill(&mut seed); - - let mnemonic = Mnemonic::from_entropy(&seed, Language::English)?; - - let phrase: &str = mnemonic.phrase(); - - let seed_res = SeedResult { - entropy: mnemonic.entropy().to_vec(), - seed_phrase: phrase.to_string(), - }; - - Ok(seed_res) -} - -///Generates a new aleo account -pub fn create_wallet() -> AvailResult> { - let avail_wallet = AvailWallet::::new()?; - - Ok(avail_wallet) -} - -/// Generates a new aleo account with a seed phrase -#[tauri::command(rename_all = "snake_case")] -pub fn gen_seeded_wallet() -> AvailResult> { - let seed_result = generate_seed_phrase()?; - let seed_phrase = seed_result.seed_phrase; - - let avail_seed_wallet = AvailSeedWallet::::new(&seed_result.entropy, seed_phrase)?; - - Ok(avail_seed_wallet) -} - #[tauri::command(rename_all = "snake_case")] pub async fn create_seed_phrase_wallet( username: Option, @@ -97,15 +32,12 @@ pub async fn create_seed_phrase_wallet( access_type: bool, backup: bool, language: Languages, + length: usize, ) -> AvailResult { - let avail_wallet = gen_seeded_wallet::()?; - let p_key = avail_wallet.private_key; - let v_key = avail_wallet.view_key; + + let avail_wallet = BetterAvailWallet::::new(length, &language)?; - let tag = match username { - Some(_) => Some(generate_discriminant()), - None => None, - }; + let tag = username.clone().map(|_| generate_discriminant()); let user_request = User { username: username.clone(), @@ -124,7 +56,7 @@ pub async fn create_seed_phrase_wallet( false, backup, avail_wallet.address.to_string(), - language, + language.clone(), )?; init_tokens_table()?; @@ -146,15 +78,22 @@ pub async fn create_seed_phrase_wallet( } }; - key_manager.store_key(&password, access_type, &p_key, &v_key)?; + key_manager.store_key( + &password, + &avail_wallet + )?; - VIEWSESSION.set_view_session(&v_key.to_string())?; + VIEWSESSION.set_view_session(&avail_wallet.get_view_key())?; PASS.set_pass_session(&password)?; - get_session_after_creation(&p_key).await?; + get_session_after_creation(&avail_wallet.private_key).await?; - Ok(avail_wallet.seed_phrase) + // NOTE: We can safely unwrap here because we created + // the wallet using the [`BetterAvailWallet::new`] method + let seed_phrase = avail_wallet.mnemonic.unwrap().phrase().to_string(); + + Ok(seed_phrase) } #[tauri::command(rename_all = "snake_case")] @@ -166,20 +105,13 @@ pub async fn import_wallet( backup: bool, language: Languages, ) -> AvailResult { - let p_key = PrivateKey::::from_str(private_key)?; - - //let private_key_bytes = private_key.to_bytes_le()?; + let avail_wallet = BetterAvailWallet::::try_from(private_key.to_string())?; - let v_key = ViewKey::::try_from(p_key)?; + let tag = username.clone().map(|_| generate_discriminant()); - //let v_key_bytes = v_key.to_bytes_le()?; - let tag = match username { - Some(_) => Some(generate_discriminant()), - None => None, - }; let user_request = User { username: username.clone(), - address: v_key.to_address().to_string(), + address: avail_wallet.address.to_string(), tag, backup: false, }; @@ -192,7 +124,7 @@ pub async fn import_wallet( tag, false, backup, - v_key.to_address().to_string(), + avail_wallet.address.to_string(), language, )?; @@ -217,391 +149,18 @@ pub async fn import_wallet( } }; - let storage = key_manager.store_key(&password, access_type, &p_key, &v_key)?; + let storage = key_manager.store_key( + &password, + &avail_wallet + )?; - VIEWSESSION.set_view_session(&v_key.to_string())?; + VIEWSESSION.set_view_session(&avail_wallet.view_key.to_string())?; PASS.set_pass_session(&password)?; - get_session_after_creation(&p_key).await?; + get_session_after_creation(&avail_wallet.private_key).await?; + get_and_store_all_data().await?; Ok(storage) } - -// TODO - Update unit tests for generation -#[cfg(test)] -mod generation_tests { - use super::*; - #[test] - fn test_create_wallet() { - let avail_wallet = create_wallet::(); - - let avail_wallet = match avail_wallet { - Ok(a) => a, - Err(_) => return, - }; - - //print wallet - print!("private key: {}", avail_wallet.private_key); - print!("view key: {}", avail_wallet.view_key); - print!("address: {}", avail_wallet.address); - } - - #[cfg(any(target_os = "ios"))] - #[test] - fn test_create_wallet_ios() { - create_wallet_ios(Some("test".to_string()), "test".to_string(), false).unwrap(); - } - - #[cfg(any(target_os = "android"))] - #[test] - fn test_create_wallet_android() { - let avail_wallet = - create_wallet_android(Some("test".to_string()), "test".to_string(), false).unwrap(); - - print!("{}", avail_wallet); - } - - #[test] - fn test_seed_wallet() { - let seed_wallet = gen_seeded_wallet::().unwrap(); - - let seed_phrase = seed_wallet.seed_phrase; - - //let seed_wallet_from_bytes = AvailSeedWallet::::from_bytes(bytes.as_slice()).unwrap(); - let seed_wallet_from_phrase = - AvailSeedWallet::::from_seed_phrase(seed_phrase.clone()).unwrap(); - - println!("2nd Seed Phrase:: {}", seed_wallet_from_phrase.seed_phrase); - println!("OG Seed Phrase:: {}", seed_phrase); - assert_eq!(seed_wallet_from_phrase.private_key, seed_wallet.private_key); - } - - #[test] - fn test_seed_phrase_generation() { - let seed_result = generate_seed_phrase().unwrap(); - - let phrase = seed_result.seed_phrase; - - println!("{}", phrase); - } -} - -/* -- Android -- */ - -// #[cfg(any(target_os = "android"))] -// #[tauri::command(rename_all = "snake_case")] -// pub async fn create_wallet_android( -// username: Option, -// password: String, -// access_type: bool, -// backup: bool, -// language: Languages, -// ) -> AvailResult { -// use crate::models::storage::languages::Languages; - -// let avail_wallet = AvailWallet::::new()?; -// let p_key = avail_wallet.private_key; -// let v_key = avail_wallet.view_key; -// let tag = match username { -// Some(_) => Some(generate_discriminant()), -// None => None, -// }; - -// let user_request = User { -// username: username.clone(), -// address: avail_wallet.address.to_string(), -// tag, -// backup: false, -// }; - -// create_user(user_request).await?; - -// initial_user_preferences( -// access_type, -// username, -// tag, -// false, -// backup, -// avail_wallet.address.to_string(), -// backup, -// language, -// )?; -// initialize_encrypted_data_table()?; - -// let key_manager = AndroidKeyController {}; -// let storage = key_manager.store_key(&password, access_type, &p_key, &v_key)?; - -// VIEWSESSION.set_view_session(&v_key.to_string())?; - -// PASS.set_pass_session(&password)?; - -// get_session_after_creation(&p_key).await?; - -// Ok(storage) -// } - -// #[cfg(any(target_os = "android"))] -// #[tauri::command(rename_all = "snake_case")] -// pub async fn create_seed_phrase_wallet_android( -// username: Option, -// password: String, -// access_type: bool, -// backup: bool, -// language: Languages, -// ) -> AvailResult { -// let avail_wallet = create_seed_phrase_wallet::()?; -// let p_key = avail_wallet.private_key; -// let v_key = avail_wallet.view_key; -// let tag = match username { -// Some(_) => Some(generate_discriminant()), -// None => None, -// }; - -// let user_request = User { -// username: username.clone(), -// address: avail_wallet.address.to_string(), -// tag, -// }; - -// create_user(user_request).await?; - -// initial_user_preferences( -// access_type, -// username, -// tag, -// false, -// backup, -// avail_wallet.address.to_string(), -// backup, -// language, -// )?; -// initialize_encrypted_data_table()?; - -// let key_manager = AndroidKeyController {}; -// let _storage = key_manager.store_key(&password, access_type, &p_key, &v_key)?; - -// VIEWSESSION.set_view_session(&v_key.to_string())?; - -// PASS.set_pass_session(&password)?; - -// get_session_after_creation(&p_key).await?; - -// Ok(avail_wallet.seed_phrase) -// } - -// /// Accepts a string representation of a private key to import an existing Aleo account -// #[cfg(any(target_os = "android"))] -// #[tauri::command(rename_all = "snake_case")] -// pub async fn import_wallet_android( -// username: Option, -// password: String, -// access_type: bool, -// private_key: &str, -// backup: bool, -// language: Languages, -// ) -> AvailResult { -// let p_key = PrivateKey::::from_str(private_key)?; - -// //let private_key_bytes = private_key.to_bytes_le()?; - -// let v_key = ViewKey::::try_from(p_key)?; - -// //let v_key_bytes = v_key.to_bytes_le()?; -// let tag = match username { -// Some(_) => Some(generate_discriminant()), -// None => None, -// }; -// let user_request = User { -// username: username.clone(), -// address: v_key.to_address().to_string(), -// tag, -// }; - -// //TODO - CHECK IF USER EXISTS -// create_user(user_request).await?; - -// initial_user_preferences( -// access_type, -// username, -// tag, -// false, -// backup, -// v_key.to_address().to_string(), -// language, -// )?; -// initialize_encrypted_data_table()?; -// store_view_session(v_key.to_bytes_le()?)?; - -// // NOTE - might have issues with JNI since function is async -// let key_manager = AndroidKeyController {}; -// let storage = key_manager.store_key(&password, access_type, &p_key, &v_key)?; - -// VIEWSESSION.set_view_session(&v_key.to_string())?; - -// PASS.set_pass_session(&password)?; - -// get_session_after_creation(&p_key).await?; -// get_and_store_all_data().await?; - -// Ok(storage) -// } - -/* -- iOS -- */ - -// #[cfg(any(target_os = "ios"))] -// #[tauri::command(rename_all = "snake_case")] -// pub async fn create_wallet_ios( -// username: Option, -// password: String, -// access_type: bool, -// backup: bool, -// language: Languages, -// ) -> AvailResult { -// let avail_wallet = AvailWallet::::new()?; - -// let p_key = avail_wallet.private_key; - -// let v_key = avail_wallet.view_key; - -// let v_key_bytes = v_key.to_bytes_le()?; - -// let tag = username.clone().map(|_| generate_discriminant()); -// let user_request = User { -// username: username.clone(), -// address: avail_wallet.address.to_string(), -// tag, -// backup: false, -// }; - -// create_user(user_request).await?; - -// //TODO: Change to mainnet on launch -// initial_user_preferences( -// access_type, -// username, -// tag, -// false, -// backup, -// avail_wallet.address.to_string(), -// language, -// )?; -// initialize_encrypted_data_table()?; - -// let key_manager = iOSKeyController {}; -// let storage = key_manager.store_key(&password, access_type, &p_key, &v_key)?; - -// VIEWSESSION.set_view_session(&v_key.to_string())?; - -// PASS.set_pass_session(&password)?; - -// get_session_after_creation(&p_key).await?; - -// Ok(storage) -// } - -// #[cfg(any(target_os = "ios"))] -// #[tauri::command(rename_all = "snake_case")] -// pub async fn create_seed_phrase_wallet_ios( -// username: Option, -// password: String, -// access_type: bool, -// backup: bool, -// language: Languages, -// ) -> AvailResult { -// let avail_wallet = create_seed_phrase_wallet::()?; - -// let p_key = avail_wallet.private_key; - -// let v_key = avail_wallet.view_key; - -// let v_key_bytes = v_key.to_bytes_le()?; -// let tag = match username { -// Some(_) => Some(generate_discriminant()), -// None => None, -// }; - -// let user_request = User { -// username: username.clone(), -// address: avail_wallet.address.to_string(), -// tag, -// }; - -// create_user(user_request).await?; - -// //TODO: Change to mainnet on launch -// initial_user_preferences( -// access_type, -// username, -// tag, -// false, -// backup, -// avail_wallet.address.to_string(), -// backup, -// language, -// )?; -// initialize_encrypted_data_table()?; - -// let key_manager = iOSKeyController {}; -// let _storage = key_manager.store_key(&password, access_type, &p_key, &v_key)?; - -// VIEWSESSION.set_view_session(&v_key.to_string())?; - -// PASS.set_pass_session(&password)?; - -// get_session_after_creation(&p_key).await?; - -// Ok(avail_wallet.seed_phrase) -// } - -// #[cfg(any(target_os = "ios"))] -// #[tauri::command(rename_all = "snake_case")] -// pub async fn import_wallet_ios( -// username: Option, -// password: String, -// access_type: bool, -// private_key: &str, -// backup: bool, -// language: Languages, -// ) -> AvailResult<()> { -// let p_key = PrivateKey::::from_str(private_key)?; - -// let v_key = ViewKey::::try_from(p_key)?; -// let tag = match username { -// Some(_) => Some(generate_discriminant()), -// None => None, -// }; - -// let user_request = User { -// username: username.clone(), -// address: v_key.to_address().to_string(), -// tag, -// }; - -// create_user(user_request).await?; - -// //Change to mainnet on launch -// initial_user_preferences( -// access_type, -// username, -// tag, -// false, -// backup, -// v_key.to_address().to_string(), -// language, -// )?; -// initialize_encrypted_data_table()?; - -// let key_manager = iOSKeyController {}; -// let _storage = key_manager.store_key(&password, access_type, &p_key, &v_key)?; - -// VIEWSESSION.set_view_session(&v_key.to_string())?; - -// PASS.set_pass_session(&password)?; - -// get_session_after_creation(&p_key).await?; -// get_and_store_all_data().await?; - -// Ok(()) -// } diff --git a/backend/src/services/account/key_management/desktop.rs b/backend/src/services/account/key_management/desktop.rs index 95409de1..5aed9917 100644 --- a/backend/src/services/account/key_management/desktop.rs +++ b/backend/src/services/account/key_management/desktop.rs @@ -1,21 +1,48 @@ use keyring::Entry; -use snarkvm::prelude::{Ciphertext, FromStr, Network, PrivateKey, ViewKey}; +use snarkvm::console::program::{FromFields, Itertools, ToFields}; +use snarkvm::prelude::{Ciphertext, FromStr, Network, PrivateKey, ViewKey,Plaintext,Literal,StringType,Field}; +use bip39::Mnemonic; use crate::{ helpers::validation::validate_secret_password, - services::local_storage::utils::encrypt_with_password, + services::local_storage::utils::{encrypt_private_key_with_password,encrypt_view_key_with_password}, + models::wallet::BetterAvailWallet }; use crate::models::storage::encryption::{Keys, Keys::PrivateKey as PKey, Keys::ViewKey as VKey}; use avail_common::{ aleo_tools::encryptor::Encryptor, - errors::{AvailError, AvailErrorType, AvailResult}, + errors::{AvailError, AvailErrorType, AvailResult} }; + +fn encrypt_seed_phrase_with_password(password: &str, seed_phrase: &str) -> AvailResult> { + + let pass_field = Field::::new_domain_separator(password); + + let seed_phrase = Plaintext::::Literal( + Literal::String(StringType::::new(seed_phrase)), + once_cell::sync::OnceCell::new(), + ); + + let cipher= seed_phrase.encrypt_symmetric(pass_field)?; + + Ok(cipher) +} + +fn decrypt_seed_phrase_with_password(ciphertext: Ciphertext,password: &str) -> AvailResult{ + let pass_field = Field::::new_domain_separator(password); + let seed_phrase = ciphertext.decrypt_symmetric(pass_field)?; + + //the seed phrase string currently looks like "\"light soon prepare wire blade charge female stage ridge happy pony chief\"" + // but needs to be "light soon prepare wire blade charge female stage ridge happy pony chief" + let seed_phrase = seed_phrase.to_string().replace("\"", ""); + + Ok(seed_phrase) +} + pub fn store( - password: &str, - _access_type: bool, - p_key: &PrivateKey, - v_key: &ViewKey, + wallet: &BetterAvailWallet, + password: &str ) -> AvailResult { //encrypt keys with password if validate_secret_password(password).is_err() { @@ -26,9 +53,17 @@ pub fn store( )); } - let ciphertext_p = encrypt_with_password::(password, PKey(*p_key))?; + let ciphertext_p = encrypt_private_key_with_password::(password, &wallet.private_key)?; + + let ciphertext_v = encrypt_view_key_with_password::(password, &wallet.view_key)?; - let ciphertext_v = encrypt_with_password::(password, VKey(*v_key))?; + if let Some(mnemonic) = &wallet.mnemonic { + let ciphertext_seed = encrypt_seed_phrase_with_password::(password, mnemonic.phrase())?; + //seed-phrase storage + let s_entry = Entry::new("com.avail.wallet.phrase", "avl-s")?; + let encrypted_seed_phrase = ciphertext_seed.to_string(); + s_entry.set_password(&encrypted_seed_phrase)?; + } //private-key storage let p_entry = Entry::new("com.avail.wallet.p", "avl-p")?; @@ -78,6 +113,16 @@ pub fn read_key(password: &str, key_type: &str) -> AvailResult(password: &str) -> AvailResult{ + let entry = Entry::new("com.avail.wallet.phrase", "avl-s")?; + let seed_phrase = entry.get_password()?; + + let seed_phrase_ciphertext = Ciphertext::::from_str(&seed_phrase)?; + let seed_phrase = decrypt_seed_phrase_with_password::(seed_phrase_ciphertext, password)?; + + Ok(seed_phrase) +} + pub fn delete_key(password: &str) -> AvailResult { // verify password is correct before deletion read_key::(password, "avl-v")?; @@ -105,9 +150,9 @@ mod windows_linux_key_management_tests { let p_key = PrivateKey::::new(&mut rng).unwrap(); let v_key = ViewKey::::try_from(&p_key).unwrap(); - let access_type = true; + let avail_wallet = BetterAvailWallet::::try_from(p_key).unwrap(); - store::(STRONG_PASSWORD, access_type, &p_key, &v_key).unwrap(); + store::(&avail_wallet,STRONG_PASSWORD).unwrap(); } #[test] @@ -115,10 +160,10 @@ mod windows_linux_key_management_tests { let mut rng = thread_rng(); let p_key = PrivateKey::::new(&mut rng).unwrap(); let v_key = ViewKey::::try_from(&p_key).unwrap(); - + let avail_wallet = BetterAvailWallet::::try_from(p_key.to_string()).unwrap(); let access_type = true; - store::(WEAK_PASSWORD, access_type, &p_key, &v_key).unwrap(); + store::(&avail_wallet,WEAK_PASSWORD).unwrap(); } #[test] @@ -126,11 +171,11 @@ mod windows_linux_key_management_tests { let mut rng = thread_rng(); let p_key = PrivateKey::::new(&mut rng).unwrap(); let v_key = ViewKey::::try_from(&p_key).unwrap(); - + let avail_wallet = BetterAvailWallet::::try_from(p_key.to_string()).unwrap(); println!("Original Private Key: {:?}", p_key); println!("Original Viewing Key: {:?}", v_key); - store::(STRONG_PASSWORD, false, &p_key, &v_key).unwrap(); + store::(&avail_wallet,STRONG_PASSWORD).unwrap(); let read_p_key = read_key::(STRONG_PASSWORD, "avl-p") .unwrap() @@ -155,9 +200,21 @@ mod windows_linux_key_management_tests { let mut rng = thread_rng(); let p_key = PrivateKey::::new(&mut rng).unwrap(); let v_key = ViewKey::::try_from(&p_key).unwrap(); + let avail_wallet = BetterAvailWallet::::try_from(p_key.to_string()).unwrap(); - store::(STRONG_PASSWORD, false, &p_key, &v_key).unwrap(); + store::(&avail_wallet,STRONG_PASSWORD).unwrap(); delete_key::(STRONG_PASSWORD).unwrap(); } + + #[test] + fn test_encrypt_seed_phrase_with_password() { + let seed_phrase = "light soon prepare wire blade charge female stage ridge happy pony chief"; + let password = "password"; + + let ciphertext = encrypt_seed_phrase_with_password::(password, seed_phrase).unwrap(); + let decrypted_seed_phrase = decrypt_seed_phrase_with_password::(ciphertext, password).unwrap(); + + assert_eq!(seed_phrase, decrypted_seed_phrase); + } } diff --git a/backend/src/services/account/key_management/key_controller.rs b/backend/src/services/account/key_management/key_controller.rs index 52239484..ebd91c3a 100644 --- a/backend/src/services/account/key_management/key_controller.rs +++ b/backend/src/services/account/key_management/key_controller.rs @@ -1,4 +1,4 @@ -use crate::models::storage::encryption::Keys; +use crate::models::{storage::encryption::Keys, wallet::BetterAvailWallet}; use crate::services::local_storage::session::password::PASS; #[cfg(target_os = "android")] @@ -9,7 +9,7 @@ use super::ios::{delete_ios, search, store_keys_local}; use snarkvm::prelude::{Identifier, Network, PrivateKey, ViewKey}; -use super::desktop::{delete_key, read_key, store}; +use super::desktop::{delete_key, read_key, store,read_seed_phrase}; use avail_common::errors::{AvailError, AvailErrorType, AvailResult}; /// This trait is used as a standard interface for the key management service. @@ -18,19 +18,12 @@ pub trait KeyController { fn store_key( &self, password: &str, - access_type: bool, - p_key: &PrivateKey, - v_key: &ViewKey, - ) -> AvailResult; - fn import_key( - &self, - password: &str, - access_type: bool, - p_key: &PrivateKey, - v_key: &ViewKey, + wallet: &BetterAvailWallet ) -> AvailResult; + fn delete_key(&self, password: Option<&str>, ext: Identifier) -> AvailResult; fn read_key(&self, password: Option<&str>, key_type: &str) -> AvailResult>; + fn read_phrase(&self, password: &str, ext: Identifier) -> AvailResult; } pub struct AndroidKeyController; @@ -40,9 +33,7 @@ impl KeyController for AndroidKeyController { fn store_key( &self, password: &str, - access_type: bool, - p_key: &PrivateKey, - v_key: &ViewKey, + wallet: BetterAvailWallet ) -> AvailResult { keystore_init(password, access_type, p_key, v_key) } @@ -50,7 +41,6 @@ impl KeyController for AndroidKeyController { fn import_key( &self, password: &str, - access_type: bool, p_key: &PrivateKey, v_key: &ViewKey, ) -> AvailResult { @@ -65,6 +55,10 @@ impl KeyController for AndroidKeyController { fn read_key(&self, password: Option<&str>, key_type: &str) -> AvailResult> { keystore_load(password, key_type) } + + fn read_phrase(&self, password: &str, ext: Identifier) -> AvailResult { + read_seed_phrase::(password) + } } pub struct iOSKeyController; @@ -74,9 +68,7 @@ impl KeyController for iOSKeyController { fn store_key( &self, password: &str, - access_type: bool, - p_key: &PrivateKey, - v_key: &ViewKey, + wallet: BetterAvailWallet ) -> AvailResult { store_keys_local(password, access_type, p_key, v_key) } @@ -99,6 +91,10 @@ impl KeyController for iOSKeyController { fn read_key(&self, password: Option<&str>, key_type: &str) -> AvailResult> { search(password, key_type) } + + fn read_phrase(&self, password: &str, ext: Identifier) -> AvailResult { + read_seed_phrase(password, ext) + } } pub struct macKeyController; @@ -108,21 +104,9 @@ impl KeyController for macKeyController { fn store_key( &self, password: &str, - access_type: bool, - p_key: &PrivateKey, - v_key: &ViewKey, - ) -> AvailResult { - store(password, access_type, p_key, v_key) - } - - fn import_key( - &self, - password: &str, - access_type: bool, - p_key: &PrivateKey, - v_key: &ViewKey, + wallet: &BetterAvailWallet ) -> AvailResult { - store(password, access_type, p_key, v_key) + store(wallet,password) } fn delete_key(&self, password: Option<&str>, _ext: Identifier) -> AvailResult { @@ -151,6 +135,10 @@ impl KeyController for macKeyController { } } } + + fn read_phrase(&self, password: &str, ext: Identifier) -> AvailResult { + read_seed_phrase::(password) + } } pub struct linuxKeyController; @@ -159,21 +147,9 @@ impl KeyController for linuxKeyController { fn store_key( &self, password: &str, - access_type: bool, - p_key: &PrivateKey, - v_key: &ViewKey, - ) -> AvailResult { - store(password, access_type, p_key, v_key) - } - - fn import_key( - &self, - password: &str, - access_type: bool, - p_key: &PrivateKey, - v_key: &ViewKey, + wallet: &BetterAvailWallet ) -> AvailResult { - store(password, access_type, p_key, v_key) + store(wallet,password) } //TODO authenticate using read_key @@ -203,6 +179,10 @@ impl KeyController for linuxKeyController { } } } + + fn read_phrase(&self, password: &str, ext: Identifier) -> AvailResult { + read_seed_phrase::(password) + } } pub struct windowsKeyController; @@ -212,21 +192,9 @@ impl KeyController for windowsKeyController { fn store_key( &self, password: &str, - access_type: bool, - p_key: &PrivateKey, - v_key: &ViewKey, - ) -> AvailResult { - store(password, access_type, p_key, v_key) - } - - fn import_key( - &self, - password: &str, - access_type: bool, - p_key: &PrivateKey, - v_key: &ViewKey, + wallet: &BetterAvailWallet ) -> AvailResult { - store(password, access_type, p_key, v_key) + store(wallet,password) } //TODO authenticate using read_key @@ -256,4 +224,8 @@ impl KeyController for windowsKeyController { } } } + + fn read_phrase(&self, password: &str, ext: Identifier) -> AvailResult { + read_seed_phrase::(password) + } } diff --git a/backend/src/services/account/phrase_recovery.rs b/backend/src/services/account/phrase_recovery.rs index 0123a1f4..d7d0ee9d 100644 --- a/backend/src/services/account/phrase_recovery.rs +++ b/backend/src/services/account/phrase_recovery.rs @@ -11,33 +11,28 @@ use crate::{ use avail_common::errors::AvailResult; use crate::api::user::{create_user, get_user}; -use crate::models::wallet::AvailSeedWallet; -use crate::services::account::key_management::key_controller::KeyController; +use crate::models::wallet::BetterAvailWallet; +use crate::services::account::key_management::key_controller::{ + linuxKeyController, macKeyController, windowsKeyController, KeyController, +}; use crate::services::authentication::session::get_session_after_creation; use crate::services::local_storage::{ encrypted_data::get_and_store_all_data, tokens::init_tokens_table, }; use avail_common::models::user::User; -/* reconstruct wallet from seed phrase and store in local storage */ - -#[cfg(target_os = "macos")] -use crate::services::account::key_management::key_controller::macKeyController; - -#[cfg(target_os = "windows")] -use crate::services::account::key_management::key_controller::windowsKeyController; - -#[cfg(target_os = "linux")] -use crate::services::account::key_management::key_controller::linuxKeyController; #[tauri::command(rename_all = "snake_case")] +/// This function provides the tauri bindings to recover an avail wallet from a seed phrase. pub async fn recover_wallet_from_seed_phrase( seed_phrase: &str, password: &str, access_type: bool, language: Languages, -) -> AvailResult<()> { - let avail_wallet = AvailSeedWallet::::from_seed_phrase(seed_phrase.to_string())?; - let address = avail_wallet.address.to_string(); +) -> AvailResult<()> { + let avail_wallet = BetterAvailWallet::::from_seed_phrase( + seed_phrase, + Languages::to_bip39_language(&language), + )?; let key_manager = { #[cfg(target_os = "macos")] @@ -56,9 +51,7 @@ pub async fn recover_wallet_from_seed_phrase( key_manager.store_key( password, - access_type, - &avail_wallet.private_key, - &avail_wallet.view_key, + &avail_wallet )?; get_session_after_creation::(&avail_wallet.private_key).await?; @@ -68,7 +61,7 @@ pub async fn recover_wallet_from_seed_phrase( Err(_) => { let request = User { username: None, - address: address.clone(), + address: avail_wallet.get_address(), tag: None, backup: false, }; @@ -79,14 +72,23 @@ pub async fn recover_wallet_from_seed_phrase( let _v_key = avail_wallet.view_key.to_bytes_le()?; - //TODO: Change to mainnet on launch - initial_user_preferences(access_type, username, tag, true, backup, address, language)?; + initial_user_preferences( + access_type, + username, + tag, + true, + backup, + avail_wallet.get_address(), + language, + )?; init_tokens_table()?; + // some function + initialize_encrypted_data_table()?; VIEWSESSION - .set_view_session(&avail_wallet.view_key.to_string()) + .set_view_session(&avail_wallet.get_view_key()) .unwrap(); if backup { @@ -95,107 +97,3 @@ pub async fn recover_wallet_from_seed_phrase( Ok(()) } - -// #[cfg(any(target_os = "android"))] -// #[tauri::command(rename_all = "snake_case")] -// pub fn recover_wallet_from_seed_phrase_android( -// seed_phrase: &str, -// password: &str, -// access_type: bool, -// language: Languages, -// ) -> AvailResult<()> { -// let avail_wallet = AvailSeedWallet::::from_seed_phrase(seed_phrase.to_string())?; -// let address = avail_wallet.address.to_string(); - -// let key_manager = AndroidKeyController; - -// key_manager.store_key( -// password, -// access_type, -// &avail_wallet.private_key, -// &avail_wallet.view_key, -// )?; - -// get_session_after_creation::(&avail_wallet.private_key).await?; - -// let (username, tag, backup) = match get_user().await { -// Ok(user) => (user.username, user.tag, user.backup), -// Err(_) => { -// let request = User { -// username: None, -// address: address.clone(), -// tag: None, -// backup: false, -// }; -// create_user(request).await?; -// (None, None, false) -// } -// }; - -// let v_key = avail_wallet.view_key.to_bytes_le()?; - -// //TODO: Change to mainnet on launch -// initial_user_preferences(access_type, username, tag, true, backup, address, language)?; -// initialize_encrypted_data_table()?; -// VIEWSESSION -// .set_view_session(&avail_wallet.view_key.to_string()) -// .unwrap(); - -// if backup { -// get_and_store_all_data().await?; -// } - -// Ok(()) -// } - -// #[cfg(any(target_os = "ios"))] -// #[tauri::command(rename_all = "snake_case")] -// pub fn recover_wallet_from_seed_phrase_ios( -// seed_phrase: &str, -// password: &str, -// access_type: bool, -// language: Languages, -// ) -> AvailResult<()> { -// let avail_wallet = AvailSeedWallet::::from_seed_phrase(seed_phrase.to_string())?; -// let address = avail_wallet.address.to_string(); - -// let key_manager = iOSKeyController; - -// key_manager.store_key( -// password, -// access_type, -// &avail_wallet.private_key, -// &avail_wallet.view_key, -// )?; - -// get_session_after_creation::(&avail_wallet.private_key).await?; - -// let (username, tag, backup) = match get_user().await { -// Ok(user) => (user.username, user.tag, user.backup), -// Err(_) => { -// let request = User { -// username: None, -// address: address.clone(), -// tag: None, -// backup: false, -// }; -// create_user(request).await?; -// (None, None, false) -// } -// }; - -// let v_key = avail_wallet.view_key.to_bytes_le()?; - -// //TODO: Change to mainnet on launch -// initial_user_preferences(access_type, username, tag, true, backup, address, language)?; -// initialize_encrypted_data_table()?; -// VIEWSESSION -// .set_view_session(&avail_wallet.view_key.to_string()) -// .unwrap(); - -// if backup { -// get_and_store_all_data().await?; -// } - -// Ok(()) -// } diff --git a/backend/src/services/account/utils.rs b/backend/src/services/account/utils.rs index 5e7577d2..95bd2704 100644 --- a/backend/src/services/account/utils.rs +++ b/backend/src/services/account/utils.rs @@ -1,6 +1,6 @@ -//function to generate a discriminant of 4 random integers - +use avail_common::errors::{AvailResult,AvailError,AvailErrorType}; use rand::Rng; +use std::process::Command; pub fn generate_discriminant() -> u32 { let mut rng = rand::thread_rng(); @@ -11,9 +11,47 @@ pub fn generate_discriminant() -> u32 { discriminant } +#[tauri::command(rename_all = "snake_case")] +pub fn open_url(url: &str) -> AvailResult<()>{ + #[cfg(target_os = "windows")] + match Command::new("cmd") + .args(&["/c", "start", url]) + .spawn(){ + Ok(_) => return Ok(()), + Err(e) => return Err(AvailError::new(AvailErrorType::Internal, format!("Error opening url: {}", e),"Error opening url".to_string())) + + }; + + #[cfg(target_os = "macos")] + match Command::new("open") + .arg(url) + .spawn(){ + Ok(_) => return Ok(()), + Err(e) => return Err(AvailError::new(AvailErrorType::Internal, format!("Error opening url: {}", e),"Error opening url".to_string())) + + }; + + #[cfg(target_os = "linux")] + match Command::new("xdg-open") + .arg(url) + .spawn(){ + Ok(_) => return Ok(()), + Err(e) => return Err(AvailError::new(AvailErrorType::Internal, format!("Error opening url: {}", e),"Error opening url".to_string())) + + }; + +} + #[test] fn test_generate_discriminant() { let discriminant = generate_discriminant(); print!("discriminant: {}", discriminant); assert!(discriminant > 999 && discriminant < 10000); } + +#[test] +fn test_open_url() { + let result = open_url("https://discord.gg/A6N5X2yX"); + assert!(result.is_ok()); +} + diff --git a/backend/src/services/authentication/session.rs b/backend/src/services/authentication/session.rs index 692aeb95..815dcc0b 100644 --- a/backend/src/services/authentication/session.rs +++ b/backend/src/services/authentication/session.rs @@ -20,9 +20,8 @@ use avail_common::{ /// Authenticates user both locally and on server. #[tauri::command(rename_all = "snake_case")] pub async fn get_session(password: Option) -> AvailResult { - let session_request = request_hash().await?; - - println!("Request Hash Response: {:?}", session_request); + let address = get_address_string()?; + let session_request = request_hash(&address).await?; let network = get_network()?; @@ -38,12 +37,14 @@ pub async fn get_session(password: Option) -> AvailResult { session_id: session_request.session_id, }; - println!("Verify Request: {:?}", verify_request); + dotenv::dotenv().ok(); + + let api = env!("API"); let client = reqwest::Client::new(); let res = client - .post(format!("http://{}:8000/auth/login/", HOST)) + .post(format!("{}/auth/login/",api)) .json(&verify_request) .send() .await?; @@ -82,9 +83,8 @@ pub async fn get_session(password: Option) -> AvailResult { pub async fn get_session_after_creation( private_key: &PrivateKey, ) -> AvailResult { - let session_request = request_hash().await?; - - println!("Request Hash Response: {:?}", session_request); + let address = Address::::try_from(private_key)?; + let session_request = request_hash(&address.to_string()).await?; let (sig, _) = sign_message_w_key::(&session_request.hash, private_key)?; @@ -93,10 +93,12 @@ pub async fn get_session_after_creation( session_id: session_request.session_id, }; - println!("Verify Request: {:?}", verify_request); + dotenv::dotenv().ok(); + + let api = env!("API"); let res = reqwest::Client::new() - .post(format!("http://{}:8000/auth/login/", HOST)) + .post(format!("{}/auth/login/",api)) .json(&verify_request) .send() .await?; @@ -119,6 +121,14 @@ pub async fn get_session_after_creation( Ok(session_request.session_id.to_string()) } else { + if res.status() == 0 { + return Err(AvailError::new( + AvailErrorType::Network, + "No internet connection".to_string(), + "No internet connection".to_string(), + )); + } + Err(AvailError::new( AvailErrorType::External, "Invalid Signature".to_string(), @@ -129,18 +139,20 @@ pub async fn get_session_after_creation( /// requests the initial hash to sign from server /// Function 1 -pub async fn request_hash() -> AvailResult { - // This function will sing a message sent by our server to verify the user and give the access to server functionality for a session. - let address = get_address_string()?; - println!("{}", address); +pub async fn request_hash(address:&str) -> AvailResult { + let client = reqwest::Client::new(); let request = server_auth::CreateSessionRequest { - public_key: address, + public_key: address.to_owned(), }; + dotenv::dotenv().ok(); + + let api = env!("API"); + let res = client - .post(format!("http://{}:8000/auth/request/", HOST)) + .post(format!("{}/auth/request/",api)) .header("Content-Type", "application/json") .json(&request) .send() @@ -149,10 +161,18 @@ pub async fn request_hash() -> AvailResult { if res.status() == 201 { Ok(res.json::().await?) } else { + if res.status() == 0 { + return Err(AvailError::new( + AvailErrorType::Network, + "No internet connection".to_string(), + "No internet connection".to_string(), + )); + } + Err(AvailError::new( AvailErrorType::External, "Error requesting auth token.".to_string(), - "".to_string(), + "Error requesting auth token.".to_string(), )) } } @@ -183,10 +203,8 @@ pub fn sign_hash( pub async fn get_session_only(request: VerifySessionResponse) -> AvailResult { let client = reqwest::Client::new(); - println!("Request for Verification: {:?}", request); - let res = client - .post(format!("http://{}:8000/auth/login/", HOST)) + .post("https://test-api.avail.global/auth/login/") .json(&request.to_request()) .send() .await?; @@ -212,6 +230,7 @@ mod tests { use crate::api::encrypted_data::get_new_transaction_messages; use crate::api::user::create_user; use crate::models::storage::languages::Languages; + use crate::models::wallet::BetterAvailWallet; use crate::services::account::key_management::desktop::{delete_key, store}; use crate::services::account::utils::generate_discriminant; use crate::services::local_storage::encrypted_data::delete_user_encrypted_data; @@ -237,7 +256,8 @@ mod tests { delete_user_preferences().unwrap(); let p_key = PrivateKey::::new(&mut rand::thread_rng()).unwrap(); - let p_key = PrivateKey::::new(&mut rand::thread_rng()).unwrap(); + let wallet = BetterAvailWallet::::try_from(p_key.to_string()).unwrap(); + let v_key = ViewKey::::try_from(&p_key).unwrap(); let tag = generate_discriminant(); @@ -264,9 +284,7 @@ mod tests { create_user(user_request).await.unwrap(); - let access_type = true; - - store::(STRONG_PASSWORD, access_type, &p_key, &v_key).unwrap(); + store::(&wallet,STRONG_PASSWORD).unwrap(); } #[tokio::test] @@ -288,7 +306,9 @@ mod tests { #[tokio::test] async fn test_request_hash() { - let hash = request_hash().await; + let address = get_address_string().unwrap(); + + let hash = request_hash(&address).await; print!("{}", hash.unwrap().hash); // assert!(hash.is_ok()); } diff --git a/backend/src/services/local_storage/encrypted_data.rs b/backend/src/services/local_storage/encrypted_data.rs index 9c7438ae..c7ed03b8 100644 --- a/backend/src/services/local_storage/encrypted_data.rs +++ b/backend/src/services/local_storage/encrypted_data.rs @@ -289,12 +289,31 @@ pub fn get_encrypted_data_by_id(id: &str) -> AvailResult { } else { Err(AvailError::new( AvailErrorType::Internal, - "No encrypted data found".to_string(), - "".to_string(), + "Data Not Found".to_string(), + "Data Not Found".to_string(), )) } } +/// get encrypted record pointer by nonce +pub fn get_encrypted_data_by_nonce(nonce: &str) -> AvailResult> { + let address = get_address_string()?; + let network = get_network()?; + + let query = format!( + "SELECT * FROM encrypted_data WHERE record_nonce='{}' AND owner='{}' AND network='{}'", + nonce, address, network + ); + + let encrypted_data = handle_encrypted_data_query(&query)?; + + if !encrypted_data.is_empty() { + Ok(Some(encrypted_data[0].clone())) + }else{ + Ok(None) + } +} + /* Main Encrypted Data funcions */ /// update encrypted data by id @@ -364,12 +383,12 @@ pub fn update_encrypted_transaction_confirmed_by_id( let storage = PersistentStorage::new()?; let query = format!( - "UPDATE encrypted_data SET ciphertext=?1, nonce=?2, program_ids=?3, function_ids=?4 WHERE id='{}'", + "UPDATE encrypted_data SET ciphertext=?1, nonce=?2, program_ids=?3, function_ids=?4, state=?5 WHERE id='{}'", id ); storage.save_mixed( - vec![&ciphertext, &nonce, &program_ids, &function_ids], + vec![&ciphertext, &nonce, &program_ids, &function_ids, &TransactionState::Confirmed.to_str()], query, )?; @@ -448,7 +467,7 @@ pub fn drop_encrypted_data_table() -> AvailResult<()> { return Err(AvailError::new( AvailErrorType::Internal, "Error dropping encrypted data table ".to_string(), - "".to_string(), + "Error deleting encrypted data table".to_string(), )) } }, @@ -659,7 +678,7 @@ mod encrypted_data_tests { //test_store_encrypted_data(); VIEWSESSION - .set_view_session("AViewKey1rWpxoch574dTmVu9zRovZ5UKyhZeBv9ftP2MkEy6TJRF") + .set_view_session("AViewKey1myvhAr2nes8MF1y8gPV19azp4evwsBR4CqyzAi62nufW") .unwrap(); let res = get_encrypted_data_by_flavour(EncryptedDataTypeCommon::Record).unwrap(); @@ -677,7 +696,7 @@ mod encrypted_data_tests { .collect::>>(); for record in records { - println!("{:?}\n", record.to_record().unwrap()); + println!("{:?}\n", record); } } diff --git a/backend/src/services/local_storage/persistent_storage.rs b/backend/src/services/local_storage/persistent_storage.rs index 2c2bff4d..3ba96b58 100644 --- a/backend/src/services/local_storage/persistent_storage.rs +++ b/backend/src/services/local_storage/persistent_storage.rs @@ -21,7 +21,7 @@ pub fn initial_user_preferences( ) -> AvailResult<()> { let storage = PersistentStorage::new()?; - let api_client = setup_local_client::(); + let api_client = setup_client::().unwrap(); let latest_height = match import { true => 0, @@ -103,9 +103,9 @@ pub fn change_auth_type() -> AvailResult<()> { let query = "SELECT auth_type FROM user_preferences".to_string(); - let res = storage.get::(query, 1)?; + let res = storage.get_all::(&query, 1)?; - let auth_type = res[0]; + let auth_type = res[0][0]; let new_auth_type = !auth_type; @@ -124,9 +124,9 @@ pub fn get_auth_type() -> AvailResult { let query = "SELECT auth_type FROM user_preferences".to_string(); - let res = storage.get::(query, 1)?; + let res = storage.get_all::(&query, 1)?; - let auth_type = res[0].clone(); + let auth_type = res[0][0].clone(); Ok(auth_type) } @@ -138,14 +138,14 @@ pub fn get_username() -> AvailResult { let query = "SELECT username || '#' || tag AS username_tag FROM user_preferences;".to_string(); - let res = storage.get::(query, 1)?; + let res = storage.get_all::(&query, 1)?; match res.get(0) { Some(username) => { - if username == "#0" { + if username[0] == "#0" { return get_address_string(); } else { - return Ok(username.clone()); + return Ok(username[0].clone()); } } None => Err(AvailError::new( @@ -173,10 +173,10 @@ pub fn get_network() -> AvailResult { let storage = PersistentStorage::new()?; let query = "SELECT network FROM user_preferences".to_string(); - let res = storage.get::(query, 1)?; + let res = storage.get_all::(&query, 1)?; match res.get(0) { - Some(network) => Ok(network.clone()), + Some(network) => Ok(network[0].clone()), None => Err(AvailError::new( AvailErrorType::LocalStorage, "No network found".to_string(), @@ -205,10 +205,10 @@ pub fn get_last_sync() -> AvailResult { let query = "SELECT last_sync FROM user_preferences".to_string(); - let res = storage.get::(query, 1)?; + let res = storage.get_all::(&query, 1)?; match res.get(0) { - Some(last_sync) => Ok(last_sync.to_owned()), + Some(last_sync) => Ok(last_sync[0].to_owned()), None => Err(AvailError::new( AvailErrorType::LocalStorage, "No last sync height found".to_string(), @@ -252,10 +252,10 @@ pub fn get_last_backup_sync() -> AvailResult> { let query = "SELECT last_backup_sync FROM user_preferences".to_string(); - let res = storage.get::>>(query, 1)?; + let res = storage.get_all::>>(&query, 1)?; match res.get(0) { - Some(last_backup) => match last_backup { + Some(last_backup) => match last_backup[0] { Some(last_backup) => Ok(last_backup.to_owned()), None => handle_no_backup_found(), }, @@ -281,9 +281,9 @@ pub fn get_last_tx_sync() -> AvailResult { let query = "SELECT last_tx_sync FROM user_preferences".to_string(); - let res = storage.get::>(query, 1)?; + let res = storage.get_all::>(&query, 1)?; - let last_tx_sync = res[0].clone().timestamp(); + let last_tx_sync = res[0][0].clone().timestamp(); Ok(last_tx_sync) } @@ -305,10 +305,10 @@ pub fn get_address() -> AvailResult> { let storage = PersistentStorage::new()?; let query = "SELECT address FROM user_preferences".to_string(); - let res = storage.get::(query, 1)?; + let res = storage.get_all::(&query, 1)?; match res.get(0) { - Some(address) => Ok(Address::::from_str(address)?), + Some(address) => Ok(Address::::from_str(&address[0])?), None => Err(AvailError::new( AvailErrorType::LocalStorage, "No address found".to_string(), @@ -322,10 +322,10 @@ pub fn get_address_string() -> AvailResult { let storage = PersistentStorage::new()?; let query = "SELECT address FROM user_preferences".to_string(); - let res = storage.get::(query, 1)?; + let res = storage.get_all::(&query, 1)?; match res.get(0) { - Some(address) => Ok(address.clone()), + Some(address) => Ok(address[0].clone()), None => Err(AvailError::new( AvailErrorType::LocalStorage, "No address found".to_string(), @@ -351,11 +351,11 @@ pub fn get_backup_flag() -> AvailResult { let query = "SELECT backup FROM user_preferences".to_string(); - let res = storage.get::(query, 1)?; + let res = storage.get_all::(&query, 1)?; let backup = res[0].clone(); - Ok(backup) + Ok(backup[0]) } #[tauri::command(rename_all = "snake_case")] @@ -376,9 +376,9 @@ pub fn get_language() -> AvailResult { let query = "SELECT language FROM user_preferences".to_string(); - let res = storage.get::(query, 1)?; + let res = storage.get_all::(&query, 1)?; - let language = match Languages::from_string_short(&res[0]) { + let language = match Languages::from_string_short(&res[0][0]) { Some(language) => language, None => Languages::English, }; @@ -415,10 +415,10 @@ fn test_initial_user_preferences() { let query = "SELECT auth_type FROM user_preferences".to_string(); - let res = storage.get::(query, 1).unwrap(); + let res = storage.get_all::(&query, 1).unwrap(); - print!("{:?}", res[0]); - assert_eq!(res[0], "true".to_string()); + print!("{:?}", res[0][0]); + assert_eq!(res[0][0], "true".to_string()); } #[test] diff --git a/backend/src/services/local_storage/storage_api/deployment.rs b/backend/src/services/local_storage/storage_api/deployment.rs index a4cfb642..e26a821e 100644 --- a/backend/src/services/local_storage/storage_api/deployment.rs +++ b/backend/src/services/local_storage/storage_api/deployment.rs @@ -54,6 +54,7 @@ pub fn find_encrypt_store_deployments( fee, TransactionState::Confirmed, Some(height), + None, timestamp, Some(timestamp), None, diff --git a/backend/src/services/local_storage/storage_api/event.rs b/backend/src/services/local_storage/storage_api/event.rs index 11195056..fc3674eb 100644 --- a/backend/src/services/local_storage/storage_api/event.rs +++ b/backend/src/services/local_storage/storage_api/event.rs @@ -18,7 +18,7 @@ use crate::services::local_storage::{ use avail_common::{ errors::{AvailError, AvailErrorType, AvailResult}, - models::encrypted_data::EncryptedDataTypeCommon, + models::encrypted_data::{EncryptedData, EncryptedDataTypeCommon}, }; /// Gets an Event by its encrypted data id @@ -100,59 +100,84 @@ pub fn get_events_raw(request: GetEventsRequest) -> AvailResult = vec![]; + + if let Some(page) = request.page { + let fetch_limit = (page * 6) + 6; + + combined_query = format!( + "{} UNION ALL {} ORDER BY created_at DESC LIMIT {}", + transitions_deployments_query_with_filters, + transactions_query_with_filters, + fetch_limit + ); + + let page_start = (page * 6) as usize; + let page_end = page_start + 6; + + let encrypted_data_result = handle_encrypted_data_query(&combined_query)?; + let page_encrypted_data = encrypted_data_result[page_start.min(encrypted_data_result.len()) + ..page_end.min(encrypted_data_result.len())] + .to_vec(); + encrypted_data = page_encrypted_data; + } else { + encrypted_data = handle_encrypted_data_query(&combined_query)?; + } let mut events: Vec = vec![]; for encrypted_transaction in encrypted_data { @@ -186,60 +211,84 @@ pub fn get_avail_events_raw(request: GetEventsRequest) -> AvailResul let address = get_address_string()?; let network = get_network()?; - let mut transitions_deployments_query = format!( - "SELECT * FROM encrypted_data WHERE flavour IN ('{}','{}') AND owner='{}' AND network='{}'", + // Query for transitions and deployments + let transitions_deployments_query = format!( + "SELECT *, '[]' as json_program_ids, '[]' as json_function_ids FROM encrypted_data WHERE flavour IN ('{}','{}') AND owner='{}' AND network='{}'", EncryptedDataTypeCommon::Transition.to_str(), EncryptedDataTypeCommon::Deployment.to_str(), address, network ); - let mut transactions_query = format!( - "SELECT * FROM encrypted_data WHERE flavour='{}' AND owner='{}' AND network='{}'", + // Query for transactions + let transactions_query = format!( + "SELECT *, program_ids as json_program_ids, function_ids as json_function_ids FROM encrypted_data WHERE flavour='{}' AND owner='{}' AND network='{}'", EncryptedDataTypeCommon::Transaction.to_str(), address, network ); + // Applying filters + let mut common_filter_conditions = String::new(); if let Some(filter) = request.filter { if let Some(event_type) = filter.event_type { - transitions_deployments_query + common_filter_conditions .push_str(&format!(" AND event_type='{}'", event_type.to_str())); - transactions_query.push_str(&format!(" AND event_type='{}'", event_type.to_str())); } if let Some(program_id) = filter.program_id { - transitions_deployments_query.push_str(&format!(" AND program_ids='{}'", program_id)); - // in the case of transactions_query the program_ids is a json string, so we need to use the json_each function - transactions_query.push_str(&format!(" AND EXISTS (SELECT 1 FROM json_each(encrypted_data.program_ids) WHERE json_each.value = '{}')", program_id)); + // Adjust this to handle both cases (string equality for transitions/deployments and JSON array contains for transactions) + common_filter_conditions.push_str(&format!( + " AND (program_ids='{}' OR JSON_EXTRACT(json_program_ids, '$') LIKE '%{}%')", + program_id, program_id + )); } - // TODO - Add function_id filter if let Some(function_id) = filter.function_id { - transitions_deployments_query.push_str(&format!(" AND function_ids='{}'", function_id)); - // in the case of transactions_query the function_ids is a json string, so we need to use the json_each function - transactions_query.push_str(&format!(" AND EXISTS (SELECT 1 FROM json_each(encrypted_data.function_ids) WHERE json_each.value = '{}')", function_id)); + // Similar adjustment for function_ids + common_filter_conditions.push_str(&format!( + " AND (function_ids='{}' OR JSON_EXTRACT(json_function_ids, '$') LIKE '%{}%')", + function_id, function_id + )); } } - transitions_deployments_query.push_str(" ORDER BY synced_on DESC"); - transactions_query.push_str(" ORDER BY synced_on DESC"); + let transitions_deployments_query_with_filters = format!( + "{} {}", + transitions_deployments_query, common_filter_conditions + ); - if let Some(page) = request.page { - println!("page: {}", page); - transitions_deployments_query.push_str(&format!(" LIMIT 6 OFFSET {}", page * 6)); - transactions_query.push_str(&format!(" LIMIT 6 OFFSET {}", page * 6)); - } + let transactions_query_with_filters = + format!("{} {}", transactions_query, common_filter_conditions); - let encrypted_transitions_deployments = - handle_encrypted_data_query(&transitions_deployments_query)?; - let encrypted_transactions = handle_encrypted_data_query(&transactions_query)?; + let mut combined_query = format!( + "{} UNION ALL {} ORDER BY created_at DESC", + transitions_deployments_query_with_filters, transactions_query_with_filters + ); - let encrypted_data = [ - &encrypted_transitions_deployments[..], - &encrypted_transactions[..], - ] - .concat(); + let mut encrypted_data: Vec = vec![]; + + if let Some(page) = request.page { + let fetch_limit = (page * 6) + 6; + + combined_query = format!( + "{} UNION ALL {} ORDER BY created_at DESC LIMIT {}", + transitions_deployments_query_with_filters, + transactions_query_with_filters, + fetch_limit + ); + + let page_start = (page * 6) as usize; + let page_end = page_start + 6; + + let encrypted_data_result = handle_encrypted_data_query(&combined_query)?; + let page_encrypted_data = encrypted_data_result[page_start.min(encrypted_data_result.len()) + ..page_end.min(encrypted_data_result.len())] + .to_vec(); + encrypted_data = page_encrypted_data; + } else { + encrypted_data = handle_encrypted_data_query(&combined_query)?; + } let mut events: Vec = vec![]; @@ -275,60 +324,84 @@ pub fn get_succinct_avail_events_raw( let address = get_address_string()?; let network = get_network()?; - let mut transitions_deployments_query = format!( - "SELECT * FROM encrypted_data WHERE flavour IN ('{}','{}') AND owner='{}' AND network='{}'", + // Query for transitions and deployments + let transitions_deployments_query = format!( + "SELECT *, '[]' as json_program_ids, '[]' as json_function_ids FROM encrypted_data WHERE flavour IN ('{}','{}') AND owner='{}' AND network='{}'", EncryptedDataTypeCommon::Transition.to_str(), EncryptedDataTypeCommon::Deployment.to_str(), address, network ); - let mut transactions_query = format!( - "SELECT * FROM encrypted_data WHERE flavour='{}' AND owner='{}' AND network='{}'", + // Query for transactions + let transactions_query = format!( + "SELECT *, program_ids as json_program_ids, function_ids as json_function_ids FROM encrypted_data WHERE flavour='{}' AND owner='{}' AND network='{}'", EncryptedDataTypeCommon::Transaction.to_str(), address, network ); + // Applying filters + let mut common_filter_conditions = String::new(); if let Some(filter) = request.filter { if let Some(event_type) = filter.event_type { - transitions_deployments_query + common_filter_conditions .push_str(&format!(" AND event_type='{}'", event_type.to_str())); - transactions_query.push_str(&format!(" AND event_type='{}'", event_type.to_str())); } if let Some(program_id) = filter.program_id { - transitions_deployments_query.push_str(&format!(" AND program_ids='{}'", program_id)); - // in the case of transactions_query the program_ids is a json string, so we need to use the json_each function - transactions_query.push_str(&format!(" AND EXISTS (SELECT 1 FROM json_each(encrypted_data.program_ids) WHERE json_each.value = '{}')", program_id)); + // Adjust this to handle both cases (string equality for transitions/deployments and JSON array contains for transactions) + common_filter_conditions.push_str(&format!( + " AND (program_ids='{}' OR JSON_EXTRACT(json_program_ids, '$') LIKE '%{}%')", + program_id, program_id + )); } - // TODO - Add function_id filter if let Some(function_id) = filter.function_id { - transitions_deployments_query.push_str(&format!(" AND function_ids='{}'", function_id)); - // in the case of transactions_query the function_ids is a json string, so we need to use the json_each function - transactions_query.push_str(&format!(" AND EXISTS (SELECT 1 FROM json_each(encrypted_data.function_ids) WHERE json_each.value = '{}')", function_id)); + // Similar adjustment for function_ids + common_filter_conditions.push_str(&format!( + " AND (function_ids='{}' OR JSON_EXTRACT(json_function_ids, '$') LIKE '%{}%')", + function_id, function_id + )); } } - transitions_deployments_query.push_str(" ORDER BY synced_on DESC"); - transactions_query.push_str(" ORDER BY synced_on DESC"); + let transitions_deployments_query_with_filters = format!( + "{} {}", + transitions_deployments_query, common_filter_conditions + ); - if let Some(page) = request.page { - println!("page: {}", page); - transitions_deployments_query.push_str(&format!(" LIMIT 6 OFFSET {}", page * 6)); - transactions_query.push_str(&format!(" LIMIT 6 OFFSET {}", page * 6)); - } + let transactions_query_with_filters = + format!("{} {}", transactions_query, common_filter_conditions); + + let mut combined_query = format!( + "{} UNION ALL {} ORDER BY created_at DESC", + transitions_deployments_query_with_filters, transactions_query_with_filters + ); - let encrypted_transitions_deployments = - handle_encrypted_data_query(&transitions_deployments_query)?; - let encrypted_transactions = handle_encrypted_data_query(&transactions_query)?; + let mut encrypted_data: Vec = vec![]; - let encrypted_data = [ - &encrypted_transitions_deployments[..], - &encrypted_transactions[..], - ] - .concat(); + if let Some(page) = request.page { + let fetch_limit = (page * 6) + 6; + + combined_query = format!( + "{} UNION ALL {} ORDER BY created_at DESC LIMIT {}", + transitions_deployments_query_with_filters, + transactions_query_with_filters, + fetch_limit + ); + + let page_start = (page * 6) as usize; + let page_end = page_start + 6; + + let encrypted_data_result = handle_encrypted_data_query(&combined_query)?; + let page_encrypted_data = encrypted_data_result[page_start.min(encrypted_data_result.len()) + ..page_end.min(encrypted_data_result.len())] + .to_vec(); + encrypted_data = page_encrypted_data; + } else { + encrypted_data = handle_encrypted_data_query(&combined_query)?; + } let mut events: Vec = vec![]; @@ -402,7 +475,7 @@ mod events_tests { use crate::{ models::wallet_connect::get_event::EventsFilter, - services::local_storage::persistent_storage::update_address, + services::local_storage::{persistent_storage::update_address, session::view::VIEWSESSION}, }; use avail_common::models::constants::TESTNET_PRIVATE_KEY; @@ -442,10 +515,22 @@ mod events_tests { #[test] fn test_get_avail_events_raw() { + VIEWSESSION + .set_view_session("AViewKey1dRUJgozQcBf2rntQqoGYfViNy4A3Khx9RZVwuX3kSNCx") + .unwrap(); let event = get_avail_events_raw::(GetEventsRequest::default()).unwrap(); println!("{:?}", event); } + #[test] + fn test_get_succinct_avail_events_raw() { + VIEWSESSION + .set_view_session("AViewKey1dRUJgozQcBf2rntQqoGYfViNy4A3Khx9RZVwuX3kSNCx") + .unwrap(); + let event = get_succinct_avail_events_raw::(GetEventsRequest::default()).unwrap(); + println!("{:?}", event); + } + #[test] fn test_transaction_pages_available() { let event_pages = transaction_pages_available().unwrap(); diff --git a/backend/src/services/local_storage/storage_api/records.rs b/backend/src/services/local_storage/storage_api/records.rs index fd44f0be..53e81dc6 100644 --- a/backend/src/services/local_storage/storage_api/records.rs +++ b/backend/src/services/local_storage/storage_api/records.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use avail_common::errors::AvailError; use snarkvm::circuit::integers::Integer; use snarkvm::circuit::{Identifier, Inject}; use snarkvm::prelude::{ @@ -11,10 +12,11 @@ use crate::models::pointers::record::{AvailRecord, Metadata, Pointer}; use crate::models::storage::persistent::PersistentStorage; use crate::models::wallet_connect::records::{GetRecordsRequest, RecordFilterType}; use crate::services::local_storage::encrypted_data::{ - get_encrypted_data_by_id, handle_encrypted_data_query, update_encrypted_data_spent_by_id, + get_encrypted_data_by_id, get_encrypted_data_by_nonce, handle_encrypted_data_query, + update_encrypted_data_spent_by_id, }; use crate::services::local_storage::persistent_storage::get_address_string; -use crate::services::local_storage::tokens::subtract_balance; +use crate::services::local_storage::tokens::{add_balance, subtract_balance}; use crate::services::local_storage::{ encrypted_data::{get_encrypted_data_by_flavour, store_encrypted_data}, persistent_storage::{get_address, get_network}, @@ -226,6 +228,92 @@ pub fn get_record_pointers_for_record_type( } /* Utilities */ + +/// Update record spent status on local storage via nonce +pub fn update_record_spent_local_via_nonce( + nonce: &str, + spent: bool, +) -> AvailResult<()> { + let address = get_address::()?; + + let v_key = VIEWSESSION.get_instance::()?; + + if let Some(encrypted_data) = get_encrypted_data_by_nonce(nonce)?{ + + let encrypted_data_id = match encrypted_data.id { + Some(id) => id.to_string(), + None => { + return Err(AvailError::new( + avail_common::errors::AvailErrorType::Internal, + "No id found for encrypted data".to_string(), + "No id found for encrypted data".to_string(), + )) + } + }; + + println!("Updating record spent {} status for id: {}",spent, encrypted_data_id); + + let encrypted_struct = encrypted_data.to_enrypted_struct::()?; + + let mut record_pointer: AvailRecord = encrypted_struct.decrypt(v_key)?; + + if record_pointer.metadata.spent == spent { + return Ok(()); + } + + record_pointer.metadata.spent = spent; + if record_pointer.clone().metadata.record_type == RecordTypeCommon::Tokens + || record_pointer.clone().metadata.record_type == RecordTypeCommon::AleoCredits + { + let record = record_pointer.clone().to_record()?; + let record_data_keys = record.data().clone().into_keys(); + let record_name = record_pointer.clone().metadata.name; + for key in record_data_keys { + let is_key: bool = matches!(key.to_string().as_str(), "amount" | "microcredits"); + + if is_key { + let balance_entry = match record.data().get(&key.clone()) { + Some(bal) => Ok(bal), + None => Err(()), + }; + let balance_f = match balance_entry.unwrap() { + Entry::Private(Plaintext::Literal(Literal::::U64(amount), _)) => amount, + _ => todo!(), + }; + let balance_field = balance_f.to_be_bytes(); + let balance = u64::from_be_bytes(balance_field); + + let _ = match spent { + true => subtract_balance(&record_name, &balance.to_string(), v_key)?, + false => add_balance(&record_name, &balance.to_string(), v_key)?, + }; + } + } + } + let updated_record_pointer = record_pointer.encrypt_for(address)?; + + update_encrypted_data_spent_by_id( + &encrypted_data_id, + &updated_record_pointer.cipher_text.to_string(), + &updated_record_pointer.nonce.to_string(), + spent, + )?; +} + + Ok(()) +} + +pub fn check_if_record_exists(nonce: &str) -> AvailResult{ + let encrypted_data = get_encrypted_data_by_nonce(nonce)?; + + if let Some(encrypted_data)= encrypted_data{ + Ok(encrypted_data.id.is_some()) + }else{ + Ok(false) + } + +} + /// Update record spent status on local storage pub fn update_record_spent_local(id: &str, spent: bool) -> AvailResult<()> { let address = get_address::()?; @@ -238,6 +326,10 @@ pub fn update_record_spent_local(id: &str, spent: bool) -> AvailResu let mut record_pointer: AvailRecord = encrypted_struct.decrypt(v_key)?; + if record_pointer.metadata.spent == spent { + return Ok(()); + } + record_pointer.metadata.spent = spent; if record_pointer.clone().metadata.record_type == RecordTypeCommon::Tokens || record_pointer.clone().metadata.record_type == RecordTypeCommon::AleoCredits @@ -246,11 +338,8 @@ pub fn update_record_spent_local(id: &str, spent: bool) -> AvailResu let record_data_keys = record.data().clone().into_keys(); let record_name = record_pointer.clone().metadata.name; for key in record_data_keys { - let is_key: bool = match key.to_string().as_str() { - "amount" => true, - "microcredits" => true, - _ => false, - }; + let is_key: bool = matches!(key.to_string().as_str(), "amount" | "microcredits"); + if is_key { let balance_entry = match record.data().get(&key.clone()) { Some(bal) => Ok(bal), @@ -263,7 +352,10 @@ pub fn update_record_spent_local(id: &str, spent: bool) -> AvailResu let balance_field = balance_f.to_be_bytes(); let balance = u64::from_be_bytes(balance_field); - let _ = subtract_balance(&record_name, &balance.to_string(), v_key); + let _ = match spent { + true => subtract_balance(&record_name, &balance.to_string(), v_key)?, + false => add_balance(&record_name, &balance.to_string(), v_key)?, + }; } } } @@ -523,4 +615,16 @@ mod records_storage_api_tests { delete_all_server_storage().await.unwrap(); } + + #[test] + fn test_check_if_record_exists() { + VIEWSESSION + .set_view_session("AViewKey1myvhAr2nes8MF1y8gPV19azp4evwsBR4CqyzAi62nufW") + .unwrap(); + + let res = check_if_record_exists::("").unwrap(); + println!("{:?}", res); + + ; + } } diff --git a/backend/src/services/local_storage/storage_api/transaction.rs b/backend/src/services/local_storage/storage_api/transaction.rs index 8340d562..3bfa303e 100644 --- a/backend/src/services/local_storage/storage_api/transaction.rs +++ b/backend/src/services/local_storage/storage_api/transaction.rs @@ -10,6 +10,7 @@ use crate::models::{ }; use crate::services::local_storage::encrypted_data::{ get_encrypted_data_by_id, handle_encrypted_data_query, handle_encrypted_data_query_params, + update_encrypted_transaction_state_by_id, }; use crate::services::local_storage::{ encrypted_data::get_encrypted_data_by_flavour, @@ -19,9 +20,11 @@ use crate::services::local_storage::{ use avail_common::{ errors::{AvailError, AvailErrorType, AvailResult}, - models::encrypted_data::{EncryptedData, EncryptedDataTypeCommon}, + models::encrypted_data::{EncryptedData, EncryptedDataTypeCommon, TransactionState}, }; +use super::{deployment::get_deployment_pointer, records::update_record_spent_local_via_nonce}; + pub fn get_transactions_exec() -> AvailResult>> { let encrypted_transactions = get_encrypted_data_by_flavour(EncryptedDataTypeCommon::Transaction)?; @@ -87,7 +90,9 @@ pub fn get_tx_ids_from_date( let address = get_address::()?; let network = get_network()?; + // get a timestamp from 2 hours ago let timestamp = date.with_timezone(&Utc); + let timestamp_2_hours_ago = timestamp - chrono::Duration::hours(2); let query = format!( "SELECT * FROM encrypted_data WHERE flavour IN ('{}','{}','{}') AND owner='{}' AND network='{}' AND created_at >= ?1", @@ -98,7 +103,8 @@ pub fn get_tx_ids_from_date( network ); - let encrypted_transactions = handle_encrypted_data_query_params(&query, vec![timestamp])?; + let encrypted_transactions = + handle_encrypted_data_query_params(&query, vec![timestamp_2_hours_ago])?; let mut transaction_ids: Vec = Vec::new(); @@ -178,6 +184,174 @@ pub fn get_transaction_ids() -> AvailResult> { Ok(transaction_ids) } +pub fn get_unconfirmed_and_failed_transaction_ids( +) -> AvailResult> { + let address = get_address::()?; + let network = get_network()?; + + let query = format!( + "SELECT * FROM encrypted_data WHERE flavour IN ('{}','{}','{}') AND state IN ('{}','{}') AND owner='{}' AND network='{}'", + EncryptedDataTypeCommon::Transition.to_str(), + EncryptedDataTypeCommon::Transaction.to_str(), + EncryptedDataTypeCommon::Deployment.to_str(), + TransactionState::Pending.to_str(), + TransactionState::Failed.to_str(), + address, + network + ); + + let encrypted_transactions = handle_encrypted_data_query(&query)?; + + let mut transaction_ids: Vec<(N::TransactionID, String)> = Vec::new(); + + for encrypted_transaction in encrypted_transactions { + let encrypted_struct = encrypted_transaction.to_enrypted_struct::()?; + match encrypted_transaction.flavour { + EncryptedDataTypeCommon::Transition => { + let transition: TransitionPointer = + encrypted_struct.decrypt(VIEWSESSION.get_instance::()?)?; + if let Some(id) = encrypted_transaction.id { + transaction_ids.push((transition.transaction_id, id.to_string())); + } + } + EncryptedDataTypeCommon::Transaction => { + let tx_exec: TransactionPointer = + encrypted_struct.decrypt(VIEWSESSION.get_instance::()?)?; + + if let Some(tx_id) = tx_exec.transaction_id() { + if let Some(id) = encrypted_transaction.id { + transaction_ids.push((tx_id, id.to_string())); + } + } + } + EncryptedDataTypeCommon::Deployment => { + let deployment: DeploymentPointer = + encrypted_struct.decrypt(VIEWSESSION.get_instance::()?)?; + if let Some(tx_id) = deployment.id { + if let Some(id) = encrypted_transaction.id { + transaction_ids.push((tx_id, id.to_string())); + } + } + } + _ => {} + }; + } + + Ok(transaction_ids) +} + +// gets unconfirmed transactions that have been unconfirmed for more than 10 minutes +fn get_expired_unconfirmed_transactions() -> AvailResult> { + let address = get_address::()?; + let network = get_network()?; + + let query = format!( + "SELECT * FROM encrypted_data WHERE flavour IN ('{}','{}','{}') AND state='{}' AND owner='{}' AND network='{}'", + EncryptedDataTypeCommon::Transition.to_str(), + EncryptedDataTypeCommon::Transaction.to_str(), + EncryptedDataTypeCommon::Deployment.to_str(), + TransactionState::Pending.to_str(), + address, + network + ); + + let now = Local::now(); + let encrypted_data = handle_encrypted_data_query(&query)?; + let mut encrypted_transactions_to_decrypt: Vec = vec![]; + + for encrypted_data in encrypted_data { + if now + .signed_duration_since(encrypted_data.created_at) + .num_minutes() + > 10 + { + println!("{:?}", encrypted_data.transaction_state.clone()); + encrypted_transactions_to_decrypt.push(encrypted_data); + } + } + Ok(encrypted_transactions_to_decrypt) +} + +// This function should get unconfirmed encryped and check if they have been unconfirmed for more than 10 minutes +// If they have this should update to failed and the records related to the transaction should be updated to unspent +pub fn check_unconfirmed_transactions() -> AvailResult<()> { + let expired_transactions = get_expired_unconfirmed_transactions::()?; + + for expired_transaction in expired_transactions { + let encrypted_struct = expired_transaction.to_enrypted_struct::()?; + match expired_transaction.flavour { + EncryptedDataTypeCommon::Transaction => { + let tx_exec: TransactionPointer = + encrypted_struct.decrypt(VIEWSESSION.get_instance::()?)?; + + if let Some(id) = expired_transaction.id { + handle_transaction_failed::(&id.to_string())?; + } + + let spent_record_nonces = tx_exec.spent_record_pointers_nonces(); + for nonce in spent_record_nonces { + update_record_spent_local_via_nonce::(&nonce, false)?; + } + } + EncryptedDataTypeCommon::Deployment => { + let deployment: DeploymentPointer = + encrypted_struct.decrypt(VIEWSESSION.get_instance::()?)?; + + if let Some(id) = expired_transaction.id { + handle_deployment_failed::(&id.to_string())?; + } + + if let Some(fee_nonce) = deployment.spent_fee_nonce { + update_record_spent_local_via_nonce::(fee_nonce.as_str(), false)?; + } + } + _ => {} + }; + } + + Ok(()) +} + +pub fn handle_transaction_failed(pointer_id: &str) -> AvailResult<()> { + let address = get_address::()?; + let mut transaction_pointer = get_transaction_pointer::(pointer_id)?; + + transaction_pointer.update_failed_transaction( + "Transaction remained unconfirmed and failed, no records were spent.".to_string(), + ); + + let encrypted_failed_transaction = transaction_pointer.to_encrypted_data(address)?; + + update_encrypted_transaction_state_by_id( + pointer_id, + &encrypted_failed_transaction.ciphertext, + &encrypted_failed_transaction.nonce, + TransactionState::Failed, + )?; + + Ok(()) +} + +pub fn handle_deployment_failed(pointer_id: &str) -> AvailResult<()> { + let address = get_address::()?; + let mut deployment_pointer = get_deployment_pointer::(pointer_id)?; + + deployment_pointer.update_failed_deployment( + "Deployment remained unconfirmed and failed, no records were spent.".to_string(), + ); + + let encrypted_failed_deployment = deployment_pointer.to_encrypted_data(address)?; + + update_encrypted_transaction_state_by_id( + pointer_id, + &encrypted_failed_deployment.ciphertext, + &encrypted_failed_deployment.nonce, + TransactionState::Failed, + )?; + + Ok(()) +} + #[cfg(test)] mod tx_out_storage_api_tests { use super::*; @@ -223,6 +397,7 @@ mod tx_out_storage_api_tests { Some("test_program_id".to_string()), Some("test_function_id".to_string()), vec![], + vec![], Local::now(), Some(Local::now() + chrono::Duration::seconds(40)), None, @@ -258,6 +433,7 @@ mod tx_out_storage_api_tests { Some("test_program_id".to_string()), Some("test_function_id".to_string()), vec![], + vec![], Local::now(), Some(Local::now() + chrono::Duration::seconds(40)), None, @@ -271,4 +447,25 @@ mod tx_out_storage_api_tests { assert_eq!(vec![test_transaction_out], transactions_out) } + + #[test] + fn test_get_unconfirmed_and_failed_transaction_ids() { + VIEWSESSION.set_view_session("AViewKey1jXL3nQ7ax6ft9qshgtTn8nXrkKNFjSBdbnjueFW5f2Gj"); + + let transactions_out = get_unconfirmed_and_failed_transaction_ids::().unwrap(); + + println!("{:?}", transactions_out); + } + + #[test] + fn test_get_transaction_ids_from_date() { + VIEWSESSION.set_view_session("AViewKey1jXL3nQ7ax6ft9qshgtTn8nXrkKNFjSBdbnjueFW5f2Gj"); + + let date = Local::now(); + let a_day_ago = date - chrono::Duration::days(1); + + let transactions_out = get_tx_ids_from_date::(a_day_ago).unwrap(); + + println!("{:?}", transactions_out); + } } diff --git a/backend/src/services/local_storage/tokens.rs b/backend/src/services/local_storage/tokens.rs index d93557d9..143addab 100644 --- a/backend/src/services/local_storage/tokens.rs +++ b/backend/src/services/local_storage/tokens.rs @@ -7,6 +7,7 @@ pub fn init_tokens_table() -> AvailResult<()> { storage.execute_query( "CREATE TABLE IF NOT EXISTS ARC20_tokens ( token_name TEXT PRIMARY KEY, + program_id TEXT NOT NULL, balance_ciphertext TEXT NOT NULL, nonce TEXT NOT NULL )", @@ -14,8 +15,28 @@ pub fn init_tokens_table() -> AvailResult<()> { Ok(()) } +pub fn drop_tokens_table() -> AvailResult<()> { + let storage = PersistentStorage::new()?; + match storage.execute_query("DROP TABLE IF EXISTS ARC20_tokens"){ + Ok(r) => r, + Err(e) => match e.error_type { + AvailErrorType::NotFound => {} + _ => { + return Err(AvailError::new( + AvailErrorType::Internal, + e.internal_msg, + "Error deleting tokens table".to_string(), + )) + } + }, + }; + + Ok(()) +} + pub fn init_token( token_name: &str, + program_id: &str, encryption_address: &str, balance: &str, ) -> AvailResult<()> { @@ -24,6 +45,7 @@ pub fn init_token( storage.execute_query( "CREATE TABLE IF NOT EXISTS ARC20_tokens ( token_name TEXT PRIMARY KEY, + program_id TEXT NOT NULL, balance_ciphertext TEXT NOT NULL, nonce TEXT NOT NULL )", @@ -36,14 +58,15 @@ pub fn init_token( let plaintext = Plaintext::::from_str(balance)?; let address = Address::::from_str(encryption_address)?; let encrypted_balance = plaintext.encrypt(&address, scalar)?; - + storage.save( vec![ token_name.to_string(), + program_id.to_string(), encrypted_balance.to_string(), nonce.to_string(), ], - "INSERT INTO ARC20_tokens (token_name, balance_ciphertext, nonce) VALUES (?1, ?2, ?3)" + "INSERT INTO ARC20_tokens (token_name, program_id, balance_ciphertext, nonce) VALUES (?1, ?2, ?3, ?4)" .to_string(), )?; @@ -60,11 +83,11 @@ pub fn add_balance( "SELECT balance_ciphertext, nonce FROM ARC20_tokens WHERE token_name='{}' ", token_name ); - let res = storage.get::(query, 2)?; + let res = storage.get_all::(&query, 2)?; match res.get(0) { Some(old_encrypted_balance) => { - let nonce = Group::::from_str(res.get(1).unwrap())?; - let ciphertext = Ciphertext::::from_str(old_encrypted_balance)?; + let nonce = Group::::from_str(res[0].get(1).unwrap())?; + let ciphertext = Ciphertext::::from_str(&old_encrypted_balance[0])?; let old_balance_string = ciphertext.decrypt(vk, nonce)?.to_string(); let old_balance = old_balance_string.trim_end_matches("u64").parse::()?; @@ -109,12 +132,12 @@ pub fn subtract_balance( "SELECT balance_ciphertext, nonce FROM ARC20_tokens WHERE token_name='{}' ", token_name ); - let res = storage.get::(query, 2)?; + let res = storage.get_all::(&query, 2)?; match res.get(0) { Some(old_encrypted_balance) => { - let nonce = res.get(1).unwrap(); + let nonce = res[0].get(1).unwrap(); let old_balance_string = Ciphertext::::decrypt( - &Ciphertext::::from_str(old_encrypted_balance)?, + &Ciphertext::::from_str(&old_encrypted_balance[0])?, vk, Group::::from_str(nonce)?, )? @@ -158,31 +181,26 @@ pub fn get_balance(token_name: &str, vk: ViewKey) -> AvailResult< "SELECT balance_ciphertext, nonce FROM ARC20_tokens WHERE token_name='{}' ", token_name ); - let res = storage.get::(query, 2)?; + let res = storage.get_all::(&query, 2)?; match res.get(0) { - Some(balance) => { - let nonce = res.get(1).unwrap(); - let balance_string = Ciphertext::::decrypt( - &Ciphertext::::from_str(balance)?, + Some(old_encrypted_balance) => { + let nonce = res[0].get(1).unwrap(); + let old_balance_string = Ciphertext::::decrypt( + &Ciphertext::::from_str(&old_encrypted_balance[0])?, vk, Group::::from_str(nonce)?, )? .to_string(); - Ok(balance_string) + Ok(old_balance_string) } - None => Err(AvailError::new( - AvailErrorType::LocalStorage, - "None_found".to_string(), - "Nonefound".to_string(), - )), + None => Ok("0u64".to_string()), } } // wrap this func and with only token id as param and vk is -// if_exists fn pub fn if_token_exists(token_name: &str) -> AvailResult { let storage = PersistentStorage::new()?; let query = format!( - "SELECT balance_ciphertext FROM ARC20_tokens WHERE token_name='{}' ", + "SELECT balance_ciphertext FROM ARC20_tokens WHERE token_name='{}'", token_name ); // let res = ?; @@ -199,6 +217,55 @@ pub fn if_token_exists(token_name: &str) -> AvailResult { } } +#[tauri::command(rename_all = "snake_case")] +pub fn get_program_id_for_token (token_name: &str) -> AvailResult{ + let storage = PersistentStorage::new()?; + let query = format!( + "SELECT program_id FROM ARC20_tokens WHERE token_name='{}'", + token_name + ); + // let res = ?; + let res = storage.get_all::(&query, 1)?; + match res.get(0) { + Some(p_id) => { + Ok(p_id[0].clone()) + } + None => Ok("".to_string()), + } +} + +#[tauri::command(rename_all = "snake_case")] +pub fn get_stored_tokens() -> AvailResult> { + let storage = PersistentStorage::new()?; + let query = "SELECT token_name FROM ARC20_tokens"; + let res = storage.get_all::(query, 1)?; + + println!("Token ids ====> {:?}", res); + + Ok(res.iter().map(|x| x[0].clone()).collect()) +} + +pub fn delete_tokens_table() -> AvailResult<()> { + let storage = PersistentStorage::new()?; + let query = "DROP TABLE ARC20_tokens"; + + match storage.execute_query(query) { + Ok(r) => r, + Err(e) => match e.error_type { + AvailErrorType::NotFound => {} + _ => { + return Err(AvailError::new( + AvailErrorType::Internal, + e.internal_msg, + "Error deleting tokens table".to_string(), + )) + } + }, + }; + + Ok(()) +} + mod test_tokens { use super::*; @@ -208,14 +275,21 @@ mod test_tokens { #[test] fn test_init() { - let api_client: AleoAPIClient = setup_local_client::(); + let api_client: AleoAPIClient = setup_client::().unwrap(); let pk = PrivateKey::::from_str(TESTNET_PRIVATE_KEY).unwrap(); let vk = ViewKey::::try_from(pk).unwrap(); - let res = init_token::("token_avl_4.record", TESTNET_ADDRESS, "100u64").unwrap(); + let res = init_token::("testnew111.record", "diff.aleo",TESTNET_ADDRESS, "100u64").unwrap(); + } + #[test] + fn test_pid() { + let api_client: AleoAPIClient = setup_client::().unwrap(); + let pk = PrivateKey::::from_str(TESTNET_PRIVATE_KEY).unwrap(); + let vk = ViewKey::::try_from(pk).unwrap(); + let res = get_program_id_for_token("testnew111.record").unwrap(); + println!("{:?}", res); } #[test] fn test_add_balance() { - let api_client: AleoAPIClient = setup_local_client::(); let pk = PrivateKey::::from_str(TESTNET_PRIVATE_KEY).unwrap(); let vk = ViewKey::::try_from(pk).unwrap(); let res = add_balance("test_token", "100u64", vk).unwrap(); @@ -224,7 +298,7 @@ mod test_tokens { #[test] fn test_subtract_balance() { - let api_client: AleoAPIClient = setup_local_client::(); + let api_client: AleoAPIClient = setup_client::().unwrap(); let pk = PrivateKey::::from_str(TESTNET_PRIVATE_KEY).unwrap(); let vk = ViewKey::::try_from(pk).unwrap(); let res = subtract_balance("token1", "100u64", vk).unwrap(); @@ -233,7 +307,7 @@ mod test_tokens { #[test] fn test_get_balance() { - let api_client: AleoAPIClient = setup_local_client::(); + let api_client: AleoAPIClient = setup_client::().unwrap(); let pk = PrivateKey::::from_str(TESTNET3_PRIVATE_KEY).unwrap(); //let vk = ViewKey::::try_from(pk).unwrap(); @@ -247,7 +321,7 @@ mod test_tokens { #[test] fn test_record_exists() { - let api_client: AleoAPIClient = setup_local_client::(); + let api_client: AleoAPIClient = setup_client::().unwrap(); let res = if_token_exists("token_not_existing").unwrap(); println!("{:?}", res); } diff --git a/backend/src/services/local_storage/utils.rs b/backend/src/services/local_storage/utils.rs index 90780232..6f0e099a 100644 --- a/backend/src/services/local_storage/utils.rs +++ b/backend/src/services/local_storage/utils.rs @@ -1,21 +1,24 @@ use std::str::FromStr; -use avail_common::models::constants::VIEW_KEY; -use bip39::Mnemonic; -use snarkvm::prelude::{ - Ciphertext, Field, Identifier, Network, PrivateKey, Signature, Testnet3, ToBytes, ViewKey, -}; - use crate::api::encrypted_data::delete_all_server_storage; use crate::api::user::delete_user; use crate::models::storage::encryption::{Keys, Keys::PrivateKey as PKey, Keys::ViewKey as VKey}; +use crate::models::storage::languages::Languages; +use crate::models::wallet::BetterAvailWallet; use crate::services::local_storage::{ encrypted_data::drop_encrypted_data_table, persistent_storage::{delete_user_preferences, get_backup_flag, get_language, get_network}, session::view::VIEWSESSION, + tokens::drop_tokens_table, +}; +use avail_common::models::constants::VIEW_KEY; +use snarkvm::prelude::{ + Ciphertext, Field, Identifier, Network, PrivateKey, Signature, Testnet3, ViewKey, }; -use crate::services::account::key_management::key_controller::KeyController; +use crate::services::account::key_management::key_controller::{ + linuxKeyController, macKeyController, windowsKeyController, KeyController, +}; use avail_common::{ aleo_tools::encryptor::Encryptor, @@ -24,15 +27,6 @@ use avail_common::{ models::{constants::PRIVATE_KEY, network::SupportedNetworks}, }; -#[cfg(target_os = "macos")] -use crate::services::account::key_management::key_controller::macKeyController; - -#[cfg(target_os = "windows")] -use crate::services::account::key_management::key_controller::windowsKeyController; - -#[cfg(target_os = "linux")] -use crate::services::account::key_management::key_controller::linuxKeyController; - #[tauri::command(rename_all = "snake_case")] pub fn get_private_key_tauri(password: Option) -> AvailResult { let network = get_network()?; @@ -51,20 +45,20 @@ pub fn get_private_key_tauri(password: Option) -> AvailResult { } pub fn get_private_key(password: Option) -> AvailResult> { - #[cfg(target_os = "android")] - let key_manager = AndroidKeyController {}; - - #[cfg(target_os = "ios")] - let key_manager = iOSKeyController {}; - - #[cfg(target_os = "macos")] - let key_manager = macKeyController {}; - - #[cfg(target_os = "windows")] - let key_manager = windowsKeyController {}; - - #[cfg(target_os = "linux")] - let key_manager = linuxKeyController {}; + let key_manager = { + #[cfg(target_os = "macos")] + { + macKeyController + } + #[cfg(target_os = "windows")] + { + windowsKeyController + } + #[cfg(target_os = "linux")] + { + linuxKeyController + } + }; let key = match password { Some(password) => key_manager.read_key(Some(&password), PRIVATE_KEY), @@ -88,27 +82,38 @@ pub fn get_private_key(password: Option) -> AvailResult) -> AvailResult { let network = get_network()?; - let language = get_language()?.to_bip39_language(); - let key = match SupportedNetworks::from_str(&network)? { + match SupportedNetworks::from_str(&network)? { SupportedNetworks::Testnet3 => { - let key = get_private_key::(password)?; - key - } - _ => { - return Err(AvailError::new( - AvailErrorType::Internal, - "Invalid network.".to_string(), - "Invalid network.".to_string(), - )) + let key_manager = { + #[cfg(target_os = "macos")] + { + macKeyController + } + #[cfg(target_os = "windows")] + { + windowsKeyController + } + #[cfg(target_os = "linux")] + { + linuxKeyController + } + }; + + let val: Identifier = Identifier::::from_str("test")?; + + let seed_phrase = match password { + Some(password) => key_manager.read_phrase(&password, val), + None => return Err(AvailError::new( + AvailErrorType::Internal, + "Password is required.".to_string(), + "Password is required.".to_string(), + )), + }?; + + Ok(seed_phrase) } - }; - - let key_bytes = key.to_bytes_le()?; - - let mnemonic = Mnemonic::from_entropy(&key_bytes, language)?; - - Ok(mnemonic.to_string()) + } } /// Get viewing key from keychain, also used as local authentication @@ -123,29 +128,24 @@ pub fn get_view_key_tauri(password: Option) -> AvailResult { Ok(key.to_string()) } - _ => Err(AvailError::new( - AvailErrorType::Internal, - "Invalid network".to_string(), - "Invalid network.".to_string(), - )), } } pub fn get_view_key(password: Option) -> AvailResult> { - #[cfg(target_os = "android")] - let key_manager = AndroidKeyController {}; - - #[cfg(target_os = "ios")] - let key_manager = iOSKeyController {}; - - #[cfg(target_os = "macos")] - let key_manager = macKeyController {}; - - #[cfg(target_os = "windows")] - let key_manager = windowsKeyController {}; - - #[cfg(target_os = "linux")] - let key_manager = linuxKeyController {}; + let key_manager = { + #[cfg(target_os = "macos")] + { + macKeyController + } + #[cfg(target_os = "windows")] + { + windowsKeyController + } + #[cfg(target_os = "linux")] + { + linuxKeyController + } + }; let key = match password { Some(password) => key_manager.read_key(Some(&password), VIEW_KEY), @@ -154,7 +154,7 @@ pub fn get_view_key(password: Option) -> AvailResult v, - Keys::PrivateKey(_) => { + _ => { return Err(AvailError::new( AvailErrorType::Internal, "Invalid key type.".to_string(), @@ -168,7 +168,7 @@ pub fn get_view_key(password: Option) -> AvailResult( password: &str, - key: Keys, + key: &Keys, ) -> AvailResult> { match key { PKey(private_key) => Ok(Encryptor::encrypt_private_key_with_secret( @@ -181,28 +181,51 @@ pub fn encrypt_with_password( } } +pub fn encrypt_private_key_with_password( + password: &str, + private_key: &PrivateKey +) -> AvailResult> { + Ok(Encryptor::encrypt_private_key_with_secret( + private_key, + password, + )?) +} + +pub fn encrypt_view_key_with_password( + password: &str, + view_key: &ViewKey +) -> AvailResult> { + Ok(Encryptor::encrypt_view_key_with_secret( + view_key, + password, + )?) +} + #[tauri::command(rename_all = "snake_case")] pub async fn delete_util(password: &str) -> AvailResult { let backup = get_backup_flag()?; - #[cfg(target_os = "android")] - let key_manager = AndroidKeyController {}; - - #[cfg(target_os = "ios")] - let key_manager = iOSKeyController {}; - - #[cfg(target_os = "macos")] - let key_manager = macKeyController {}; - - #[cfg(target_os = "windows")] - let key_manager = windowsKeyController {}; - - #[cfg(target_os = "linux")] - let key_manager = linuxKeyController {}; + let key_manager = { + #[cfg(target_os = "macos")] + { + macKeyController + } + #[cfg(target_os = "windows")] + { + windowsKeyController + } + #[cfg(target_os = "linux")] + { + linuxKeyController + } + }; let val: Identifier = Identifier::::from_str("test")?; - key_manager.delete_key(Some(password), val)?; + match key_manager.delete_key(Some(password), val) { + Ok(_) => {} + Err(e) => {} + }; // delete encrypted data drop_encrypted_data_table()?; @@ -210,17 +233,56 @@ pub async fn delete_util(password: &str) -> AvailResult { // delete user preferences delete_user_preferences()?; + // delete tokens + drop_tokens_table()?; + // if backup delete server side storage if backup { - //delete_all_server_storage().await?; + delete_all_server_storage().await?; } // delete server user - //delete_user().await?; + delete_user().await?; Ok("Deleted.".to_string()) } +#[tauri::command(rename_all = "snake_case")] +pub fn delete_local_for_recovery(password: &str) -> AvailResult<()>{ + let key_manager = { + #[cfg(target_os = "macos")] + { + macKeyController + } + #[cfg(target_os = "windows")] + { + windowsKeyController + } + #[cfg(target_os = "linux")] + { + linuxKeyController + } + }; + + let val: Identifier = Identifier::::from_str("test")?; + + match key_manager.delete_key(Some(password), val) { + Ok(_) => {} + Err(e) => {} + }; + + // delete encrypted data + drop_encrypted_data_table()?; + + // delete user preferences + delete_user_preferences()?; + + // delete tokens + drop_tokens_table()?; + + Ok(()) +} + // Sign any string pub fn sign_message( message: &str, @@ -261,9 +323,24 @@ pub fn sign_message_w_key( mod test_utils { use super::*; use avail_common::models::constants::STRONG_PASSWORD; + use snarkvm::utilities::ToBytes; #[tokio::test] async fn test_delete() { delete_util(STRONG_PASSWORD).await.unwrap(); } + + #[test] + fn test_several_pk_bytes() { + let pk1 = PrivateKey::::new(&mut rand::thread_rng()).unwrap(); + + let pk2 = PrivateKey::::new(&mut rand::thread_rng()).unwrap(); + + let pk1_bytes = pk1.to_bytes_le().unwrap(); + + let pk2_bytes = pk2.to_bytes_le().unwrap(); + + print!("PK1: {:?}", pk1_bytes); + print!("PK2 {:?}", pk2_bytes); + } } diff --git a/backend/src/services/record_handling/decrypt_transition.rs b/backend/src/services/record_handling/decrypt_transition.rs index f07f1d62..e8d0876b 100644 --- a/backend/src/services/record_handling/decrypt_transition.rs +++ b/backend/src/services/record_handling/decrypt_transition.rs @@ -138,20 +138,7 @@ impl DecryptTransition { //check inputs for (index, input) in transition.inputs().iter().enumerate() { - if let Input::Private(id, ciphertext_option) = input { - if let Some(ciphertext) = ciphertext_option { - let index_field = Field::from_u16(u16::try_from(index)?); - let input_view_key = N::hash_psd4(&[function_id, tvk, index_field])?; - let plaintext = match ciphertext.decrypt_symmetric(input_view_key) { - Ok(plaintext) => plaintext, - Err(_) => { - continue; - } - }; - let input = Input::Public(*id, Some(plaintext)); - decrypted_inputs.push(input.clone()); - } //record inputs are handled later or should be handled directly here to store pointer with it if record was inputted but not executed by user - } else if let Input::Record(_id, _checksum) = input { + if let Input::Record(_id, _checksum) = input { // spent records should be handled here let input_tag = match input.tag() { Some(tag) => tag, @@ -162,6 +149,7 @@ impl DecryptTransition { if &record_pointer.tag()? == input_tag { update_record_spent_local::(id, true)?; spent_input_ids.push(id.to_string()); + println!("================> INSIDE Input::Record"); let mock_input = Input::::Public(Uniform::rand(&mut rand::thread_rng()), None); @@ -201,6 +189,10 @@ impl DecryptTransition { continue; } }; + println!( + "================> INSIDE Output::Private ----> PlainText -- {:?}", + plaintext + ); let output = Output::Public(*id, Some(plaintext)); decrypted_outputs.push(output.clone()); } @@ -216,6 +208,7 @@ impl DecryptTransition { index, )?; + // println!("================> INSIDE Output::Record"); match record_pointer { Some(record_pointer) => { record_pointers.push(record_pointer.clone()); @@ -224,6 +217,7 @@ impl DecryptTransition { store_encrypted_data(encrypted_record_pointer)?; decrypted_outputs.push(output.clone()); + println!("================> INSIDE record_pointer"); } None => continue, } @@ -252,6 +246,7 @@ impl DecryptTransition { amount, block_height, ); + println!("================> INSIDE !decrypted_inputs.is_empty()"); let encrypted_transition_pointer = transition_pointer.to_encrypted_data(address)?; store_encrypted_data(encrypted_transition_pointer.clone())?; @@ -272,7 +267,7 @@ impl DecryptTransition { amount, block_height, ); - + println!("================> INSIDE !decrypted_outputs.is_empty()"); let encrypted_transition_pointer = transition_pointer.to_encrypted_data(address)?; store_encrypted_data(encrypted_transition_pointer.clone())?; diff --git a/backend/src/services/record_handling/docs.md b/backend/src/services/record_handling/docs.md new file mode 100644 index 00000000..47481e32 --- /dev/null +++ b/backend/src/services/record_handling/docs.md @@ -0,0 +1,4 @@ +# Notes on Record Handling + +## The tag system +`tag` construction, the `tag` is not stored with the record as tag but can be computed via `N::hash_psd2(&[sk_tag, commitment])` as we do where `sk_tag` can only be derived from your `view_key` , and then the tag is stored on chain when this record is spent so you can literally query a block for all the tags it stores and these would be all the tags of records spent within that block. But you cannot check if a specific record has been spent without the viewing key. \ No newline at end of file diff --git a/backend/src/services/record_handling/records.rs b/backend/src/services/record_handling/records.rs index 019ce812..7269834e 100644 --- a/backend/src/services/record_handling/records.rs +++ b/backend/src/services/record_handling/records.rs @@ -1,31 +1,44 @@ -use snarkvm::prelude::{Network, Plaintext, Record}; +use chrono::Local; +use snarkvm::{ + console::program::Itertools, + prelude::{ConfirmedTransaction, Network, Plaintext, Record}, +}; +use std::ops::Sub; +use tauri::{Manager, Window}; -//use rayon::prelude::*; use crate::{ api::aleo_client::{setup_client, setup_local_client}, helpers::utils::get_timestamp_from_i64, models::wallet_connect::records::{GetRecordsRequest, RecordFilterType, RecordsFilter}, services::{ local_storage::{ - encrypted_data::handle_block_scan_failure, + encrypted_data::{ + handle_block_scan_failure, update_encrypted_transaction_confirmed_by_id, + update_encrypted_transaction_state_by_id, + }, persistent_storage::{get_address_string, update_last_sync}, session::view::VIEWSESSION, storage_api::{ - deployment::find_encrypt_store_deployments, + deployment::{find_encrypt_store_deployments, get_deployment_pointer}, records::{get_record_pointers, get_record_pointers_for_record_type}, - transaction::get_tx_ids_from_date, + transaction::{ + check_unconfirmed_transactions, get_transaction_pointer, get_tx_ids_from_date, + get_unconfirmed_and_failed_transaction_ids, + }, }, }, - record_handling::utils::sync_transaction, + record_handling::utils::{ + get_executed_transitions, handle_deployment_confirmed, handle_deployment_rejection, + handle_transaction_confirmed, handle_transaction_rejection, input_spent_check, + sync_transaction, transition_to_record_pointer, + }, }, }; -use std::ops::Sub; -use tauri::{Manager, Window}; use avail_common::{ aleo_tools::program_manager::Credits, errors::{AvailError, AvailErrorType, AvailResult}, - models::encrypted_data::{EncryptedData, RecordTypeCommon}, + models::encrypted_data::{EncryptedData, RecordTypeCommon, TransactionState}, }; /// Scans the blockchain for new records, distills record pointers, transition pointer and tags, and returns them @@ -37,7 +50,7 @@ pub fn get_records( let view_key = VIEWSESSION.get_instance::()?; let address = view_key.to_address(); - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let step_size = 49; @@ -46,8 +59,33 @@ pub fn get_records( let last_sync_block = api_client.get_block(last_sync)?; let last_sync_timestamp = get_timestamp_from_i64(last_sync_block.timestamp())?; + // checks if unconfirmed transactions have expired and updates their state to failed + check_unconfirmed_transactions::()?; + let stored_transaction_ids = get_tx_ids_from_date::(last_sync_timestamp)?; + println!("Stored transaction ids: {:?}", stored_transaction_ids); + + let unconfirmed_and_failed_ids = get_unconfirmed_and_failed_transaction_ids::()?; + + println!( + "Unconfirmed and failed ids: {:?}", + unconfirmed_and_failed_ids + ); + + let unconfirmed_and_failed_transaction_ids = unconfirmed_and_failed_ids + .iter() + .map(|(id, _)| *id) + .collect::>(); + + let stored_transaction_ids = stored_transaction_ids + .iter() + .filter(|id| !unconfirmed_and_failed_transaction_ids.contains(id)) + .cloned() + .collect_vec(); + + println!("Stored transaction ids without unconfirmed and failed: {:?}", stored_transaction_ids); + let mut end_height = last_sync.saturating_add(step_size); let mut start_height = last_sync; @@ -59,7 +97,6 @@ pub fn get_records( for _ in (last_sync..latest_height).step_by(step_size as usize) { let mut blocks = api_client.get_blocks(start_height, end_height)?; - //blocks.reverse(); for block in blocks { // Check for deployment transactions @@ -67,20 +104,341 @@ pub fn get_records( let timestamp = get_timestamp_from_i64(block.clone().timestamp())?; let height = block.height(); - find_encrypt_store_deployments( + match find_encrypt_store_deployments( transactions, height, timestamp, address, stored_transaction_ids.clone(), - )?; + ) { + Ok(_) => {} + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error scanning deployment transactions.".to_string(), + )); + } + } for transaction in transactions.iter() { - if stored_transaction_ids.contains(&transaction.id()) { + let transaction_id = transaction.id(); + + let unconfirmed_transaction_id = match transaction.to_unconfirmed_transaction_id() { + Ok(id) => id, + Err(_) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::SnarkVm, + "Error getting unconfirmed transaction id".to_string(), + "Issue getting unconfirmed transaction id".to_string(), + )); + } + }; + + if stored_transaction_ids.contains(&transaction_id) + || stored_transaction_ids.contains(&unconfirmed_transaction_id) + { + continue; + } + + if let Some((tx_id, pointer_id)) = + unconfirmed_and_failed_ids.iter().find(|(tx_id, _)| { + tx_id == &transaction_id || tx_id == &unconfirmed_transaction_id + }) + { + + let inner_tx = transaction.transaction(); + let fee = match inner_tx.fee_amount() { + Ok(fee) => *fee as f64 / 1000000.0, + Err(_) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::SnarkVm, + "Error calculating fee".to_string(), + "Issue calculating fee".to_string(), + )); + } + }; + + if let ConfirmedTransaction::::AcceptedExecute(_, _, _) = transaction { + let executed_transitions = + match get_executed_transitions::(inner_tx, height) { + Ok(transitions) => transitions, + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::SnarkVm, + e.to_string(), + "Error getting executed transitions".to_string(), + )); + } + }; + + match handle_transaction_confirmed( + pointer_id.as_str(), + *tx_id, + executed_transitions, + height, + timestamp, + Some(fee), + address, + ) { + Ok(_) => {} + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error handling confirmed transaction".to_string(), + )); + } + }; + + continue; + } else if let ConfirmedTransaction::::AcceptedDeploy(_, _, _) = transaction { + if let Some(fee_transition) = transaction.fee_transition() { + let transition = fee_transition.transition(); + + match input_spent_check(transition, true) { + Ok(_) => {} + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error checking spent input".to_string(), + )); + } + }; + + match transition_to_record_pointer( + *tx_id, + transition.clone(), + height, + view_key, + ) { + Ok(_) => {} + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error finding records from transition".to_string(), + )); + } + }; + } + + match handle_deployment_confirmed( + pointer_id.as_str(), + *tx_id, + height, + Some(fee), + address, + ) { + Ok(_) => {} + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error handling confirmed deployment".to_string(), + )); + } + }; + + continue; + } else if let ConfirmedTransaction::::RejectedDeploy(_, fee_tx, _, _) = + transaction + { + let deployment_pointer = + match get_deployment_pointer::(pointer_id.as_str()) { + Ok(pointer) => pointer, + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error getting deployment pointer".to_string(), + )); + } + }; + + if let Some(fee_transition) = fee_tx.fee_transition() { + let transition = fee_transition.transition(); + + match input_spent_check(transition, true) { + Ok(_) => {} + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error checking spent input".to_string(), + )); + } + }; + + match transition_to_record_pointer( + *tx_id, + transition.clone(), + height, + view_key, + ) { + Ok(_) => {} + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error finding records from transition".to_string(), + )); + } + }; + } + + match handle_deployment_rejection( + deployment_pointer, + pointer_id.as_str(), + *tx_id, + height, + Some(fee), + address, + ) { + Ok(_) => {} + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error handling rejected deployment".to_string(), + )); + } + }; + + continue; + } else if let ConfirmedTransaction::::RejectedExecute( + _, + fee_tx, + rejected_tx, + _, + ) = transaction + { + let transaction_pointer = + match get_transaction_pointer::(pointer_id.as_str()) { + Ok(pointer) => pointer, + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error getting transaction pointer".to_string(), + )); + } + }; + + if let Some(fee_transition) = fee_tx.fee_transition() { + let transition = fee_transition.transition(); + + match input_spent_check(transition, true) { + Ok(_) => {} + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error checking spent input".to_string(), + )); + } + }; + + match transition_to_record_pointer( + *tx_id, + transition.clone(), + height, + view_key, + ) { + Ok(_) => {} + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error finding records from transition".to_string(), + )); + } + }; + } + + if let Some(rejected_execution) = rejected_tx.execution() { + match handle_transaction_rejection( + transaction_pointer, + pointer_id.as_str(), + Some(rejected_execution.clone()), + Some(*tx_id), + height, + Some(fee), + address, + ) { + Ok(_) => {} + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error handling rejected transaction".to_string(), + )); + } + }; + + continue; + } + + match handle_transaction_rejection( + transaction_pointer, + pointer_id.as_str(), + None, + Some(*tx_id), + height, + Some(fee), + address, + ) { + Ok(_) => {} + Err(e) => { + handle_block_scan_failure::(height)?; + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error handling rejected transaction".to_string(), + )); + } + }; + + continue; + } continue; } - let transaction_result = + let (_, _, _, bool_flag) = match sync_transaction::(transaction, height, timestamp, None, None) { Ok(transaction_result) => transaction_result, Err(e) => { @@ -103,23 +461,40 @@ pub fn get_records( } }; - found_flag = transaction_result.0.is_some() - || !transaction_result.1.is_empty() - || !transaction_result.2.is_empty(); + if !found_flag { + found_flag = bool_flag; + } } - // if anything fails before here, all data relate to {height} must be purged. - println!("Syncing.. {}", height); - update_last_sync(height)?; + match update_last_sync(height) { + Ok(_) => {println!("Synced {}", height);} + Err(e) => { + + match handle_block_scan_failure::(height) { + Ok(_) => {} + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error syncing transaction".to_string(), + )); + } + } + + return Err(AvailError::new( + AvailErrorType::Internal, + e.to_string(), + "Error updating last synced block height".to_string(), + )); + } + }; } - // calculate percentage of blocks scanned at each step let percentage = (((end_height - last_sync) as f32 / (latest_height - last_sync) as f32) * 10000 as f32) .round() / 100.0; - // if percentage is greater than 100, set it to 100 let percentage = if percentage > 100.0 { 100.0 } else { @@ -150,7 +525,6 @@ pub fn get_records( }; } - println!("Found flag: {}", found_flag); Ok(found_flag) } @@ -363,7 +737,7 @@ mod record_handling_test { let start = 500527u32; let end = 500531u32; - let api_client = setup_local_client::(); + let api_client = setup_client::().unwrap(); let blocks = api_client.get_blocks(start, end).unwrap(); @@ -404,7 +778,7 @@ mod record_handling_test { #[test] fn test_get_records() { - let api_client = setup_local_client::(); + let api_client = setup_client::().unwrap(); let latest_height = api_client.latest_height().unwrap(); let last_sync = get_last_sync().unwrap(); diff --git a/backend/src/services/record_handling/sync.rs b/backend/src/services/record_handling/sync.rs index 722af6cf..10a03ba6 100644 --- a/backend/src/services/record_handling/sync.rs +++ b/backend/src/services/record_handling/sync.rs @@ -46,7 +46,7 @@ fn process_transaction( if let Some(transaction) = transaction { println!("Transaction verified"); - let (_, record_pointers, encrypted_transitions) = sync_transaction( + let (_, record_pointers, encrypted_transitions, _) = sync_transaction( &transaction, transaction_message.confirmed_height(), timestamp, @@ -77,7 +77,7 @@ pub async fn txs_sync() -> AvailResult { /// syncs transactions sent to user by another avail user pub async fn txs_sync_raw() -> AvailResult { - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let backup = get_backup_flag()?; @@ -86,6 +86,8 @@ pub async fn txs_sync_raw() -> AvailResult { let (txs_in, ids) = get_new_transaction_messages::().await?; + println!("Transactions In: {:?}",txs_in); + if txs_in == vec![] { let res = TxScanResponse { txs: false, @@ -239,11 +241,11 @@ pub async fn sync_backup() -> AvailResult<()> { // get timestamp from block let api_client = match SupportedNetworks::from_str(&network)? { - SupportedNetworks::Testnet3 => setup_local_client::(), - _ => setup_local_client::(), + SupportedNetworks::Testnet3 => setup_client::(), + _ => setup_client::(), }; - let block = api_client.get_block(last_sync)?; + let block = api_client?.get_block(last_sync)?; let timestamp = get_timestamp_from_i64_utc(block.timestamp())?; update_last_backup_sync(timestamp) @@ -337,7 +339,6 @@ mod test { use crate::services::account::key_management::key_controller::windowsKeyController; use snarkvm::prelude::{AleoID, Field, FromStr, PrivateKey, Testnet3, ToBytes, ViewKey}; - use tauri::command::CommandArg; fn test_setup_prerequisites() { let pk = PrivateKey::::from_str(TESTNET_PRIVATE_KEY).unwrap(); @@ -361,7 +362,7 @@ mod test { VIEWSESSION.set_view_session(&view_key.to_string()).unwrap(); } - + #[tokio::test] async fn test_blocks_scan() { //NOTE - Don't forget to change OS depending on what you testing on -default should be linux @@ -452,7 +453,7 @@ mod test { tokio::time::sleep(tokio::time::Duration::from_secs(45)).await; - let api_client = setup_local_client::(); + let api_client = setup_client::().unwrap(); let latest_height = api_client.latest_height().unwrap(); @@ -465,7 +466,7 @@ mod test { async fn test_scan() { test_setup_prerequisites(); - let api_client = setup_local_client::(); + let api_client = setup_client::().unwrap(); let latest_height = api_client.latest_height().unwrap(); blocks_sync_test(latest_height).await.unwrap(); @@ -569,7 +570,7 @@ output r1 as u32.public;", let pk2 = PrivateKey::::from_str(TESTNET3_PRIVATE_KEY).unwrap(); - let api_client = setup_local_client::(); + let api_client = setup_client::().unwrap(); let mut program_manager = ProgramManager::::new(Some(pk2), None, Some(api_client.clone()), None) @@ -716,7 +717,7 @@ output r1 as u32.public;", #[test] fn test_get_latest_height() { - let api_client = setup_local_client::(); + let api_client = setup_client::().unwrap(); let latest_height = api_client.latest_height().unwrap(); println!("latest_height: {:?}", latest_height); @@ -797,7 +798,7 @@ output r1 as u32.public;", tokio::time::sleep(tokio::time::Duration::from_secs(30)).await; - let api_client = setup_local_client::(); + let api_client = setup_client::().unwrap(); let latest_height2 = api_client.latest_height().unwrap(); blocks_sync_test(latest_height2).await.unwrap(); } diff --git a/backend/src/services/record_handling/transfer.rs b/backend/src/services/record_handling/transfer.rs index 24c76e53..213ee498 100644 --- a/backend/src/services/record_handling/transfer.rs +++ b/backend/src/services/record_handling/transfer.rs @@ -9,7 +9,7 @@ use std::fs; use std::{ops::Add, str::FromStr}; use tokio::time::{Duration, Instant}; -use crate::api::aleo_client::setup_local_client; +use crate::api::aleo_client::setup_client; use crate::services::local_storage::encrypted_data::update_encrypted_transaction_state_by_id; use crate::{ helpers::utils::get_timestamp_from_i64, @@ -130,7 +130,7 @@ async fn transfer_private_util( password: Option, window: Option, ) -> AvailResult { - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let sender_address = get_address::()?; @@ -140,6 +140,7 @@ async fn transfer_private_util( let _session_task = get_session_after_creation::(&private_key).await?; let recipient = get_address_from_recipient::(to).await?; + let mut record_nonces: Vec = vec![]; let program_manager = ProgramManager::::new(Some(private_key), None, Some(api_client.clone()), None).unwrap(); @@ -147,11 +148,15 @@ async fn transfer_private_util( // get required records if private tx let (token_record, _token_commitment, token_id) = find_tokens_to_spend::(asset_id, amount, vec![])?; + let token_nonce = token_record.nonce().to_string(); + record_nonces.push(token_nonce); let (fee_record, _fee_commitment, fee_id) = match fee_private { true => { let (fee_record, _fee_commitment, fee_id) = find_aleo_credits_record_to_spend::(fee, vec![])?; + let fee_nonce = fee_record.nonce().to_string(); + record_nonces.push(fee_nonce); (Some(fee_record), Some(_fee_commitment), Some(fee_id)) } false => (None, None, None), @@ -167,6 +172,7 @@ async fn transfer_private_util( Some(program_id.clone()), Some("transfer_private".to_string()), vec![], + record_nonces, Local::now(), None, message, @@ -179,7 +185,16 @@ async fn transfer_private_util( let pending_tx_id = pending_transaction.encrypt_and_store(sender_address)?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &pending_tx_id)?; + match window.emit("tx_state_change", &pending_tx_id){ + Ok(_) => {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; let amount = amount.to_owned(); @@ -225,7 +240,16 @@ async fn transfer_private_util( )?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &pending_tx_id)?; + match window.emit("tx_state_change", &pending_tx_id){ + Ok(_) => {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; return Err(AvailError::new( @@ -268,13 +292,14 @@ async fn transfer_public_to_private_util( password: Option, window: Option, ) -> AvailResult { - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let sender_address = get_address::()?; let private_key = get_private_key::(password)?; //extend session auth let _session_task = get_session_after_creation::(&private_key).await?; let recipient = get_address_from_recipient::(to).await?; + let mut record_nonces: Vec = vec![]; let program_manager = ProgramManager::::new(Some(private_key), None, Some(api_client.clone()), None)?; @@ -286,6 +311,10 @@ async fn transfer_public_to_private_util( true => { let (fee_record, _fee_commitment, fee_id) = find_aleo_credits_record_to_spend::(fee, vec![])?; + + let fee_nonce = fee_record.nonce().to_string(); + record_nonces.push(fee_nonce); + update_record_spent_local::(&fee_id, true)?; (Some(fee_record), Some(_fee_commitment), Some(fee_id)) } @@ -300,6 +329,7 @@ async fn transfer_public_to_private_util( Some(program_id.clone()), Some("transfer_public_to_private".to_string()), vec![], + record_nonces, Local::now(), None, message, @@ -312,7 +342,16 @@ async fn transfer_public_to_private_util( let pending_tx_id = pending_transaction.encrypt_and_store(sender_address)?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &pending_tx_id)?; + match window.emit("tx_state_change", &pending_tx_id){ + Ok(_) => {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + } }; // update spent states @@ -351,7 +390,16 @@ async fn transfer_public_to_private_util( )?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &pending_tx_id)?; + match window.emit("tx_state_change", &pending_tx_id){ + Ok(_) => {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; return Err(AvailError::new( @@ -387,13 +435,14 @@ async fn transfer_private_to_public_util( password: Option, window: Option, ) -> AvailResult { - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let sender_address = get_address::()?; let private_key = get_private_key::(password)?; //extend session auth let _session_task = get_session_after_creation::(&private_key).await?; let recipient = get_address_from_recipient::(to).await?; + let mut record_nonces: Vec = vec![]; let program_manager = ProgramManager::::new(Some(private_key), None, Some(api_client.clone()), None).unwrap(); @@ -401,11 +450,17 @@ async fn transfer_private_to_public_util( // get required records if private tx let (token_record, _token_commitment, token_id) = find_tokens_to_spend::(asset_id, amount, vec![])?; + let token_nonce = token_record.nonce().to_string(); + record_nonces.push(token_nonce); let (fee_record, _fee_commitment, fee_id) = match fee_private { true => { let (fee_record, _fee_commitment, fee_id) = find_aleo_credits_record_to_spend::(fee, vec![])?; + + let fee_nonce = fee_record.nonce().to_string(); + record_nonces.push(fee_nonce); + update_record_spent_local::(&fee_id, true)?; (Some(fee_record), Some(_fee_commitment), Some(fee_id)) } @@ -422,6 +477,7 @@ async fn transfer_private_to_public_util( Some(program_id.clone()), Some("transfer_private_to_public".to_string()), vec![], + record_nonces, Local::now(), None, message, @@ -434,7 +490,16 @@ async fn transfer_private_to_public_util( let pending_tx_id = pending_transaction.encrypt_and_store(sender_address)?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &pending_tx_id)?; + match window.emit("tx_state_change", &pending_tx_id){ + Ok(_) => {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; // update spent states @@ -476,7 +541,16 @@ async fn transfer_private_to_public_util( )?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &pending_tx_id)?; + match window.emit("tx_state_change", &pending_tx_id){ + Ok(_) => {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; return Err(AvailError::new( @@ -512,7 +586,7 @@ async fn transfer_public( password: Option, window: Option, ) -> AvailResult { - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let sender_address = get_address::()?; let private_key = get_private_key::(password)?; @@ -523,11 +597,17 @@ async fn transfer_public( let program_manager = ProgramManager::::new(Some(private_key), None, Some(api_client.clone()), None)?; + let mut record_nonces: Vec = vec![]; + // get required records if private fee let (fee_record, _fee_commitment, fee_id) = match fee_private { true => { let (fee_record, _fee_commitment, fee_id) = find_aleo_credits_record_to_spend::(fee, vec![])?; + + let fee_nonce = fee_record.nonce().to_string(); + record_nonces.push(fee_nonce); + update_record_spent_local::(&fee_id, true)?; (Some(fee_record), Some(_fee_commitment), Some(fee_id)) } @@ -544,6 +624,7 @@ async fn transfer_public( Some(program_id.clone()), Some("transfer_public".to_string()), vec![], + record_nonces, Local::now(), None, message, @@ -556,7 +637,16 @@ async fn transfer_public( let pending_tx_id = pending_transaction.encrypt_and_store(sender_address)?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &pending_tx_id)?; + match window.emit("tx_state_change", &pending_tx_id){ + Ok(_) => {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; // update spent states @@ -595,7 +685,16 @@ async fn transfer_public( )?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &pending_tx_id)?; + match window.emit("tx_state_change", &pending_tx_id){ + Ok(_) => {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; return Err(AvailError::new( @@ -630,9 +729,10 @@ pub fn find_confirmed_block_height( DateTime, TransactionState, Option, + Option>, Option, )> { - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let latest_block_height = api_client.latest_height()?; @@ -640,7 +740,7 @@ pub fn find_confirmed_block_height( let mut iter = latest_block_height; let start_time = Instant::now(); - let search_duration = Duration::from_secs(120); + let search_duration = Duration::from_secs(180); println!("Waiting for transaction to confirm in block on chain"); while !flag && start_time.elapsed() < search_duration { @@ -680,6 +780,7 @@ pub fn find_confirmed_block_height( TransactionState::Aborted, None, None, + None, )); } @@ -695,27 +796,39 @@ pub fn find_confirmed_block_height( flag = true; let timestamp = get_timestamp_from_i64(block.timestamp())?; let fee = tx.transaction().fee_amount()?; - let transitions = tx.transitions().cloned().collect::>(); + let mut transitions: Vec> = vec![]; + + if let Some(fee_transition) = tx.transaction().fee_transition() { + let transition = fee_transition.transition(); + transitions.push(transition.clone()); + } + return Ok(( iter, - vec![], + transitions, timestamp, TransactionState::Aborted, None, + None, Some(*fee as f64 / 1000000.0), )); } else if let ConfirmedTransaction::::AcceptedExecute(_, _, _) = tx { flag = true; + println!("C1"); let timestamp = get_timestamp_from_i64(block.timestamp())?; + println!("C2"); let fee = tx.transaction().fee_amount()?; + println!("C3"); let transitions = tx.transitions().cloned().collect::>(); + println!("C4"); return Ok(( iter, transitions, timestamp, TransactionState::Confirmed, None, + None, Some(*fee as f64 / 1000000.0), )); } @@ -726,37 +839,64 @@ pub fn find_confirmed_block_height( flag = true; let timestamp = get_timestamp_from_i64(block.timestamp())?; let fee = tx.transaction().fee_amount()?; - let transitions = tx.transitions().cloned().collect::>(); + + let mut transitions: Vec> = vec![]; + + if let Some(fee_transition) = tx.transaction().fee_transition() { + let transition = fee_transition.transition(); + transitions.push(transition.clone()); + } + return Ok(( iter, transitions, timestamp, TransactionState::Rejected, Some(fee_tx.id()), + None, Some(*fee as f64 / 1000000.0), )); } - } else if let ConfirmedTransaction::::RejectedExecute(_, fee_tx, _, _) = tx { + } else if let ConfirmedTransaction::::RejectedExecute( + _, + fee_tx, + rejected_execution, + _, + ) = tx + { if tx.to_unconfirmed_transaction_id()? == tx_id { flag = true; let timestamp = get_timestamp_from_i64(block.timestamp())?; let fee = tx.transaction().fee_amount()?; let transitions = tx.transitions().cloned().collect::>(); + if let Some(rejected_execution) = rejected_execution.execution() { + return Ok(( + iter, + transitions, + timestamp, + TransactionState::Rejected, + Some(fee_tx.id()), + Some(rejected_execution.to_owned()), + Some(*fee as f64 / 1000000.0), + )); + } + return Ok(( iter, transitions, timestamp, TransactionState::Rejected, Some(fee_tx.id()), + None, Some(*fee as f64 / 1000000.0), )); } } - - iter = iter.add(1); - std::thread::sleep(std::time::Duration::from_secs(7)); } } + + iter = iter.add(1); + std::thread::sleep(std::time::Duration::from_secs(7)); } Err(AvailError::new( @@ -832,7 +972,6 @@ mod transfer_tests { }; use crate::services::account::generation::import_wallet; - #[cfg(target_os = "macos")] use crate::services::account::key_management::key_controller::KeyController; use crate::services::local_storage::session::view::VIEWSESSION; use crate::services::local_storage::{ @@ -1031,6 +1170,7 @@ mod transfer_tests { #[cfg(target_os = "macos")] let mac_key_controller = macKeyController {}; + #[cfg(target_os = "macos")] mac_key_controller .delete_key(Some(STRONG_PASSWORD), ext) .unwrap(); @@ -1172,7 +1312,7 @@ mod transfer_tests { /* -- Has to be called here cause has to await-- */ - let pk = PrivateKey::::from_str(TESTNET_PRIVATE_KEY).unwrap(); + let pk = PrivateKey::::from_str(TESTNET3_PRIVATE_KEY).unwrap(); let ext = Identifier::::from_str("test").unwrap(); #[cfg(target_os = "macos")] @@ -1214,7 +1354,7 @@ mod transfer_tests { let fee = 4000000u64; let amount = 100000u64; - let recipient_address = Address::::from_str(TESTNET3_ADDRESS).unwrap(); + let recipient_address = Address::::from_str("").unwrap(); let asset_id = "credits".to_string(); let request = TransferRequest::new( @@ -1234,7 +1374,7 @@ mod transfer_tests { // Transfer funds to test wallet on local dev network #[tokio::test] async fn test_transfer_public_to_private_util() { - let api_client = setup_local_client::(); + let api_client = setup_client::().unwrap(); let private_key = PrivateKey::::from_str(TESTNET_PRIVATE_KEY).unwrap(); let program_manager = ProgramManager::::new( diff --git a/backend/src/services/record_handling/utils.rs b/backend/src/services/record_handling/utils.rs index 66d9131d..ad54dd2c 100644 --- a/backend/src/services/record_handling/utils.rs +++ b/backend/src/services/record_handling/utils.rs @@ -1,9 +1,12 @@ +use avail_common::models::encrypted_data::EncryptedDataTypeCommon; use chrono::{DateTime, Local}; use snarkvm::circuit::Aleo; +use snarkvm::console::network::Testnet3; use snarkvm::ledger::transactions::ConfirmedTransaction; use snarkvm::prelude::{ - Address, Ciphertext, Entry, Field, GraphKey, Identifier, Itertools, Literal, Network, Output, - Plaintext, ProgramID, Record, RecordType, Transition, Value, ViewKey, + Address, Ciphertext, Entry, Execution, Field, GraphKey, Identifier, Itertools, Literal, + Network, Output, Plaintext, ProgramID, Record, RecordType, Transaction, Transition, Value, + ViewKey, }; use snarkvm::synthesizer::program::{Command, Instruction, ProgramCore}; use snarkvm::utilities::ToBits; @@ -22,6 +25,7 @@ use crate::api::{ use crate::helpers::validation::validate_address_bool; use crate::models::event::EventTransition; use crate::models::pointers::{ + deployment::DeploymentPointer, message::TransactionMessage, record::AvailRecord, transaction::{ExecutedTransition, TransactionPointer}, @@ -29,8 +33,9 @@ use crate::models::pointers::{ use crate::models::wallet_connect::balance::Balance; use crate::models::wallet_connect::records::{GetRecordsRequest, RecordFilterType, RecordsFilter}; +use crate::services::local_storage::encrypted_data::get_encrypted_data_by_flavour; use crate::services::local_storage::tokens::{ - add_balance, get_balance, if_token_exists, init_token, + add_balance, get_balance, get_program_id_for_token, if_token_exists, init_token }; use crate::services::local_storage::{ encrypted_data::{ @@ -42,8 +47,8 @@ use crate::services::local_storage::{ storage_api::{ deployment::get_deployment_pointer, records::{ - encrypt_and_store_records, get_record_pointers, update_record_spent_local, - update_records_spent_backup, + check_if_record_exists, encrypt_and_store_records, get_record_pointers, + update_record_spent_local, update_records_spent_backup, }, transaction::get_transaction_pointer, }, @@ -61,7 +66,7 @@ use super::decrypt_transition::DecryptTransition; /// Gets all tags from a given block height to the latest block height pub fn get_tags(min_block_height: u32) -> AvailResult> { - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let latest_height = api_client.latest_height()?; let step = 49; @@ -92,7 +97,7 @@ fn spent_checker( block_height: u32, local_tags: Vec, ) -> AvailResult<(Vec, Vec)> { - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let latest_height = api_client.latest_height()?; let step = 49; @@ -185,7 +190,11 @@ pub fn transition_to_record( } } -pub fn input_spent_check(transition: &Transition) -> AvailResult> { +/// Updates spent state of records if the tag matches the transition input tag +pub fn input_spent_check( + transition: &Transition, + spent: bool, +) -> AvailResult> { let inputs = transition.inputs(); let mut spent_ids: Vec = vec![]; @@ -207,7 +216,7 @@ pub fn input_spent_check(transition: &Transition) -> AvailResult< for (record_pointer, id) in record_pointers.iter().zip(ids.iter()) { if &record_pointer.tag()? == input_tag { - update_record_spent_local::(id, true)?; + update_record_spent_local::(id, spent)?; spent_ids.push(id.to_string()); } } @@ -239,25 +248,45 @@ pub fn get_record_type( let functions = program.functions().clone().into_keys(); for function in functions { - match function.to_string().as_str() { - "approve_public" => token_count = token_count + 1, - "unapprove_public" => token_count = token_count + 1, - "transfer_from_public" => token_count = token_count + 1, - "transfer_public" => token_count = token_count + 1, - "transfer_private" => token_count = token_count + 1, - "transfer_private_to_public" => token_count = token_count + 1, - "transfer_public_to_private" => token_count = token_count + 1, + let fn_str = function.to_string(); + match fn_str.as_str() { + _ if fn_str.contains("approve_public") => token_count = token_count + 1, + _ if fn_str.contains("unapprove_public") => token_count = token_count + 1, + _ if fn_str.contains("transfer_from_public") => token_count = token_count + 1, + _ if fn_str.contains("transfer_public") => token_count = token_count + 1, + _ if fn_str.contains("transfer") => token_count = token_count + 1, + _ if fn_str.contains("transfer_private") => token_count = token_count + 1, + _ if fn_str.contains("transfer_private_to_public") => token_count = token_count + 1, + _ if fn_str.contains("transfer_public_to_private") => token_count = token_count + 1, + _ if fn_str.contains("mint") => token_count = token_count + 1, + _ if fn_str.contains("mint_private") => token_count = token_count + 1, + _ if fn_str.contains("split") => token_count = token_count + 1, + _ if fn_str.contains("join") => token_count = token_count + 1, + _ if fn_str.contains("deposit") => token_count = token_count + 1, + _ if fn_str.contains("withdraw") => token_count = token_count + 1, + _ if fn_str.contains("burn") => token_count = token_count + 1, + _ if fn_str.contains("create") => token_count = token_count + 1, + _ if fn_str.contains("init") => token_count = token_count + 1, + // -------- NFT -------- - "initialize_collection" => nft_count = nft_count + 1, - "add_nft" => nft_count = nft_count + 1, - "add_minter" => nft_count = nft_count + 1, - "update_toggle_settings" => nft_count = nft_count + 1, - "set_mint_block" => nft_count = nft_count + 1, - "update_symbol" => nft_count = nft_count + 1, - "update_base_uri" => nft_count = nft_count + 1, - "open_mint" => nft_count = nft_count + 1, - "mint" => nft_count = nft_count + 1, - "claim_nft" => nft_count = nft_count + 1, + _ if fn_str.contains("initialize") => nft_count = nft_count + 1, + _ if fn_str.contains("initialize_collection") => nft_count = nft_count + 1, + _ if fn_str.contains("add_nft") => nft_count = nft_count + 1, + _ if fn_str.contains("admin") => nft_count = nft_count + 1, + _ if fn_str.contains("register") => nft_count = nft_count + 1, + _ if fn_str.contains("minting") => nft_count = nft_count + 1, + _ if fn_str.contains("add_minter") => nft_count = nft_count + 1, + _ if fn_str.contains("update_toggle_settings") => nft_count = nft_count + 1, + _ if fn_str.contains("set_mint_block") => nft_count = nft_count + 1, + _ if fn_str.contains("transfer") => nft_count = nft_count + 1, + _ if fn_str.contains("update_symbol") => nft_count = nft_count + 1, + _ if fn_str.contains("update_base_uri") => nft_count = nft_count + 1, + _ if fn_str.contains("open_mint") => nft_count = nft_count + 1, + _ if fn_str.contains("mint") => nft_count = nft_count + 1, + _ if fn_str.contains("claim_nft") => nft_count = nft_count + 1, + _ if fn_str.contains("claim") => nft_count = nft_count + 1, + _ if fn_str.contains("burn_nft") => nft_count = nft_count + 1, + _ if fn_str.contains("burn") => nft_count = nft_count + 1, _ => {} } } @@ -290,7 +319,7 @@ pub fn transition_to_record_pointer( let address = view_key.to_address(); let address_x_coordinate = address.to_x_coordinate(); let sk_tag = GraphKey::try_from(view_key)?.sk_tag(); - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let outputs = transition.outputs(); let mut records: Vec> = vec![]; @@ -313,6 +342,11 @@ pub fn transition_to_record_pointer( } }; + match check_if_record_exists::(&record.nonce().to_string())? { + true => continue, + false => {} + } + let program_id = transition.program_id(); let mut record_type = RecordTypeCommon::None; let program = api_client.get_program(program_id)?; @@ -355,6 +389,7 @@ pub fn transition_to_record_pointer( )?; init_token::( record_name.clone().as_str(), + &program_id.to_string(), view_key.to_address().to_string().as_str(), balance.to_string().as_str(), )?; @@ -377,6 +412,7 @@ pub fn transition_to_record_pointer( )?; let encrypted_record_pointer = record_pointer.to_encrypted_data(address)?; + store_encrypted_data(encrypted_record_pointer)?; Some(record_pointer) } @@ -415,11 +451,19 @@ pub fn get_record_type_and_amount( None => Err(()), }; let balance_f = match balance_entry.unwrap() { - Entry::Private(Plaintext::Literal(Literal::::U64(amount), _)) => amount, + Entry::Private(Plaintext::Literal(Literal::::U64(amount), _)) => { + let balance_field = amount.to_be_bytes(); + balance = format!("{}u64", u64::from_be_bytes(balance_field).to_string()); + } + Entry::Private(Plaintext::Literal(Literal::::U128(amount), _)) => { + let balance_field = amount.to_be_bytes(); + let bal_t = u128::from_be_bytes(balance_field); + balance = format!("{}u64", u64::try_from(bal_t)?.to_string()); + } _ => todo!(), }; - let balance_field = balance_f.to_be_bytes(); - balance = format!("{}u64", u64::from_be_bytes(balance_field).to_string()); + // let balance_field = balance_f.to_be_bytes(); + // balance = format!("{}u64", u64::from_be_bytes(balance_field).to_string()); } } Ok(balance) @@ -450,7 +494,12 @@ pub fn output_to_record_pointer( Err(_) => return Ok((None, None)), }; - let api_client = setup_local_client::(); + match check_if_record_exists::(&record.nonce().to_string())? { + true => return Ok((None, None)), + false => {} + } + + let api_client = setup_client::()?; let program = api_client.get_program(program_id)?; let record_name = get_record_name(program.clone(), function_id, index)?; let mut balance = "".to_string(); @@ -493,6 +542,7 @@ pub fn output_to_record_pointer( )?; init_token::( record_name.clone().as_str(), + &program_id.to_string(), view_key.to_address().to_string().as_str(), balance.to_string().as_str(), )?; @@ -522,6 +572,88 @@ pub fn output_to_record_pointer( } } +// #[tauri::command(rename_all = "snake_case")] +// pub fn get_all_nft_data() -> AvailResult> { +// const network: Network = match SupportedNetworks::try_from(get_network()?)? { +// SupportedNetworks::Testnet3 => Testnet3, +// }; + +// let nft_data = get_all_nft_raw::()?; +// Ok(nft_data) +// } + +pub fn get_all_nft_raw() -> AvailResult> { + VIEWSESSION.set_view_session("").unwrap(); + + let nft_encrypted_data = + get_encrypted_data_by_flavour(EncryptedDataTypeCommon::Record).unwrap(); + let v_key = VIEWSESSION.get_instance::().unwrap(); + + let records = nft_encrypted_data + .iter() + .map(|x| { + let encrypted_data = x.to_enrypted_struct::().unwrap(); + let block: AvailRecord = encrypted_data.decrypt(v_key).unwrap(); + block + }) + .collect::>>(); + + let plaintexts: Vec>> = records + .iter() + .filter(|&record| record.metadata.record_type == RecordTypeCommon::NFT) // NFT Records + .filter_map(|record: &AvailRecord| match record.to_record() { + Ok(record) => Some(record), + Err(e) => None, + }) + .collect::>(); + + fn u128_to_string(u: u128) -> String { + let mut temp_u128 = u; + let mut bytes = vec![] as Vec; + + while temp_u128 > 0u128 { + let byte = (temp_u128 & 0xff) as u8; + bytes.push(byte); + temp_u128 >>= 8; + } + + bytes.reverse(); + + String::from_utf8(bytes).unwrap() + } + + let full_urls = plaintexts + .iter() + .filter_map(|record| { + let data1 = record + .data() + .clone() + .get(&Identifier::::from_str("data1").unwrap()) + .cloned()?; + + let data2 = record + .data() + .clone() + .get(&Identifier::::from_str("data2").unwrap()) + .cloned()?; + + match (data1, data2) { + ( + Entry::Private(Plaintext::Literal(Literal::::U128(data1), _)), + Entry::Private(Plaintext::Literal(Literal::::U128(data2), _)), + ) => { + let data1 = u128_to_string(*data1); + let data2 = u128_to_string(*data2); + Some(format!("{}{}", data1, data2)) + } + _ => None, + } + }) + .collect::>(); + + Ok(full_urls) +} + /// Helper to parse the mapping value from the program mapping fn parse_with_suffix(input: &str) -> Result { //remove last three characters @@ -532,9 +664,14 @@ fn parse_with_suffix(input: &str) -> Result { /// Get public balance for any ARC20 token pub fn get_public_token_balance(asset_id: &str) -> AvailResult { let address = get_address_string()?; - let program_id = format!("{}.aleo", asset_id); - - let api_client = setup_local_client::(); + let record_name = format!("{}.record", asset_id); + // let program_id = format!("{}.aleo", asset_id); + let mut program_id = get_program_id_for_token(&record_name)?; + if (program_id == ""){ + program_id = format!("{}.aleo", asset_id); + } + println!("===> PROGRAM ID FOR FETCH {:?}", program_id); + let api_client = setup_client::()?; let credits_mapping = match api_client.get_mapping_value(program_id, "account", &address) { Ok(credits_mapping) => credits_mapping, @@ -554,8 +691,11 @@ pub fn get_public_token_balance(asset_id: &str) -> AvailResult /// Get private balance for any ARC20 token pub fn get_private_token_balance(asset_id: &str) -> AvailResult { let address = get_address_string()?; - let program_id = format!("{}.aleo", asset_id); let record_name = format!("{}.record", asset_id); + let program_id = format!("{}.aleo", asset_id); + println!("===> Asset ID in get_private_balance() {:?}", asset_id); + + let vk = VIEWSESSION.get_instance::()?; let balance = get_balance(&record_name, vk)?; @@ -567,9 +707,12 @@ pub fn get_private_token_balance(asset_id: &str) -> AvailResult /// Get Arc20 Token Balance pub fn get_token_balance(asset_id: &str) -> AvailResult { - let public = get_public_token_balance::(asset_id)?; - let private = get_private_token_balance::(asset_id)?; - + let asset_id_modified = asset_id.to_string(); + let asset_id_final = asset_id.to_string().replace(".record",""); + println!("===> Asset ID after mpod {:?}", asset_id_final); + let public = get_public_token_balance::(&asset_id_final)?; + let private = get_private_token_balance::(&asset_id_final)?; + println!("===> Token Balance of {:?}", asset_id_final); println!("public: {:?}", public); println!("private: {:?}", private); @@ -606,12 +749,30 @@ pub async fn handle_encrypted_storage_and_message( )?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &transaction_pointer_id)?; + match window.emit("tx_state_change", &transaction_pointer_id) { + Ok(_) => {} + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; // search for the transaction on chain - let (block_height, transitions, timestamp, transaction_state, rejected_tx_id, fee) = - find_confirmed_block_height::(transaction_id)?; + let ( + block_height, + transitions, + timestamp, + transaction_state, + fee_tx_id, + rejected_execution, + fee, + ) = find_confirmed_block_height::(transaction_id)?; + + println!("State of transaction: {:?}", transaction_state); if transaction_state == TransactionState::Rejected { // input record was not spent in this case @@ -619,25 +780,28 @@ pub async fn handle_encrypted_storage_and_message( update_record_spent_local::(&input_id, false)?; } - processing_transaction_pointer.update_rejected_transaction( - "Transaction rejected by the Aleo blockchain.".to_string(), - rejected_tx_id, + //TODO - Do not double subtract input id + handle_transaction_rejection( + processing_transaction_pointer, + transaction_pointer_id, + rejected_execution, + fee_tx_id, block_height, fee, - ); - - let updated_encrypted_transaction = - processing_transaction_pointer.to_encrypted_data(sender_address)?; - - update_encrypted_transaction_state_by_id( - transaction_pointer_id, - &updated_encrypted_transaction.ciphertext, - &updated_encrypted_transaction.nonce, - TransactionState::Rejected, + sender_address, )?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &transaction_pointer_id)?; + match window.emit("tx_state_change", &transaction_pointer_id) { + Ok(_) => {} + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; // Check for remainder of private fee given back as new record @@ -672,7 +836,16 @@ pub async fn handle_encrypted_storage_and_message( )?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &transaction_pointer_id)?; + match window.emit("tx_state_change", &transaction_pointer_id) { + Ok(_) => {} + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; return Ok(()); @@ -689,7 +862,7 @@ pub async fn handle_encrypted_storage_and_message( if !transition.is_fee_private() { if wallet_connect { - let mut transition_spent_ids = match input_spent_check::(&transition) { + let mut transition_spent_ids = match input_spent_check::(&transition, true) { Ok(transition_spent_ids) => transition_spent_ids, Err(e) => { return Err(AvailError::new( @@ -722,51 +895,20 @@ pub async fn handle_encrypted_storage_and_message( .collect::>>>>()? .concat(); - let mut pending_transaction_pointer = get_transaction_pointer::(transaction_pointer_id)?; - - println!("Updating to confirmed!"); - pending_transaction_pointer.update_confirmed_transaction( + handle_transaction_confirmed::( + transaction_pointer_id, transaction_id, - block_height, execution_transitions, + block_height, timestamp, - TransactionState::Confirmed, fee, - ); - - let updated_encrypted_transaction = - pending_transaction_pointer.to_encrypted_data(sender_address)?; - - let program_ids = match updated_encrypted_transaction.clone().program_ids { - Some(program_ids) => program_ids, - None => { - return Err(AvailError::new( - AvailErrorType::Internal, - "Program ids not found".to_string(), - "Program ids not found".to_string(), - )) - } - }; - - let function_ids = match updated_encrypted_transaction.clone().function_ids { - Some(function_ids) => function_ids, - None => { - return Err(AvailError::new( - AvailErrorType::Internal, - "Function ids not found".to_string(), - "Function ids not found".to_string(), - )) - } - }; - - update_encrypted_transaction_confirmed_by_id( - transaction_pointer_id, - &updated_encrypted_transaction.ciphertext, - &updated_encrypted_transaction.nonce, - &program_ids, - &function_ids, + sender_address, )?; + println!("Inshallah confirmed"); + let mut confirmed_transaction_pointer = get_transaction_pointer::(transaction_pointer_id)?; + println!("{:?}", confirmed_transaction_pointer); + //check if private fee was spent if let Some(fee_id) = fee_id { update_record_spent_local::(&fee_id, true)?; @@ -784,7 +926,16 @@ pub async fn handle_encrypted_storage_and_message( } if let Some(window) = window.clone() { - window.emit("tx_state_change", &transaction_pointer_id)?; + match window.emit("tx_state_change", &transaction_pointer_id) { + Ok(_) => {} + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; if sender_address != recipient_address { @@ -792,7 +943,7 @@ pub async fn handle_encrypted_storage_and_message( transaction_id, block_height, username, - pending_transaction_pointer.message(), + processing_transaction_pointer.message(), ); let encrypted_transaction_message = @@ -804,7 +955,6 @@ pub async fn handle_encrypted_storage_and_message( Ok(()) } -// TODO - Add transition inputs spent check via input type /// Handles updating pending transaction and encrypted storage pub async fn handle_transaction_update_and_encrypted_storage( transaction_id: N::TransactionID, @@ -832,30 +982,55 @@ pub async fn handle_transaction_update_and_encrypted_storage( )?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &transaction_pointer_id)?; + match window.emit("tx_state_change", &transaction_pointer_id) { + Ok(_) => {} + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; - let (block_height, transitions, timestamp, transaction_state, rejected_tx_id, fee) = - find_confirmed_block_height::(transaction_id)?; + let ( + block_height, + transitions, + timestamp, + transaction_state, + fee_tx_id, + rejected_execution, + fee, + ) = find_confirmed_block_height::(transaction_id)?; if transaction_state == TransactionState::Rejected { - processing_transaction_pointer.update_rejected_transaction( - "Transaction rejected by the Aleo blockchain.".to_string(), - rejected_tx_id, + // Check for remainder of private fee given back as new record + for transition in transitions { + transition_to_record_pointer(transaction_id, transition, block_height, view_key)?; + } + + handle_transaction_rejection( + processing_transaction_pointer, + transaction_pointer_id, + rejected_execution, + fee_tx_id, block_height, fee, - ); - let updated_encrypted_transaction = - processing_transaction_pointer.to_encrypted_data(sender_address)?; - update_encrypted_transaction_state_by_id( - transaction_pointer_id, - &updated_encrypted_transaction.ciphertext, - &updated_encrypted_transaction.nonce, - TransactionState::Rejected, + sender_address, )?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &transaction_pointer_id)?; + match window.emit("tx_state_change", &transaction_pointer_id) { + Ok(_) => {} + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; return Ok(()); @@ -865,6 +1040,8 @@ pub async fn handle_transaction_update_and_encrypted_storage( update_record_spent_local::(&fee_id, false)?; } + //TODO - Update record spent state for input nonces + processing_transaction_pointer.update_aborted_transaction( "Transaction aborted by the Aleo blockchain. No tokens were spent.".to_string(), transaction_id, @@ -880,7 +1057,16 @@ pub async fn handle_transaction_update_and_encrypted_storage( )?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &transaction_pointer_id)?; + match window.emit("tx_state_change", &transaction_pointer_id) { + Ok(_) => {} + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; return Ok(()); @@ -891,20 +1077,22 @@ pub async fn handle_transaction_update_and_encrypted_storage( let records = transitions .iter() - .filter(|transition| !transition.is_fee_private() && !transition.is_fee_public()) + .filter(|transition| !transition.is_fee_public()) .map(|transition| { let transition = transition.to_owned(); - let mut transition_spent_ids = input_spent_check::(&transition)?; - spent_ids.append(&mut transition_spent_ids); + if !transition.is_fee_private() { + let mut transition_spent_ids = input_spent_check::(&transition, true)?; + spent_ids.append(&mut transition_spent_ids); - let executed_transition = ExecutedTransition::::new( - transition.program_id().to_string(), - transition.function_name().to_string(), - transition.id().to_owned(), - ); + let executed_transition = ExecutedTransition::::new( + transition.program_id().to_string(), + transition.function_name().to_string(), + transition.id().to_owned(), + ); - execution_transitions.push(executed_transition); + execution_transitions.push(executed_transition); + } let record_pointer = transition_to_record_pointer(transaction_id, transition, block_height, view_key)?; @@ -913,50 +1101,17 @@ pub async fn handle_transaction_update_and_encrypted_storage( .collect::>>>>()? .concat(); - let mut pending_transaction_pointer = get_transaction_pointer::(transaction_pointer_id)?; - - pending_transaction_pointer.update_confirmed_transaction( + handle_transaction_confirmed::( + transaction_pointer_id, transaction_id, - block_height, execution_transitions, + block_height, timestamp, - TransactionState::Confirmed, fee, - ); - - let updated_encrypted_transaction = - pending_transaction_pointer.to_encrypted_data(sender_address)?; - - let program_ids = match updated_encrypted_transaction.clone().program_ids { - Some(program_ids) => program_ids, - None => { - return Err(AvailError::new( - AvailErrorType::Internal, - "Program ids not found".to_string(), - "Program ids not found".to_string(), - )) - } - }; - - let function_ids = match updated_encrypted_transaction.clone().function_ids { - Some(function_ids) => function_ids, - None => { - return Err(AvailError::new( - AvailErrorType::Internal, - "Function ids not found".to_string(), - "Function ids not found".to_string(), - )) - } - }; - - update_encrypted_transaction_confirmed_by_id( - transaction_pointer_id, - &updated_encrypted_transaction.ciphertext, - &updated_encrypted_transaction.nonce, - &program_ids, - &function_ids, + sender_address, )?; + //TODO - Do not double subtract fee //check if private fee was spent if let Some(fee_id) = fee_id { update_record_spent_local::(&fee_id, true)?; @@ -966,12 +1121,22 @@ pub async fn handle_transaction_update_and_encrypted_storage( } if let Some(window) = window.clone() { - window.emit("tx_state_change", &transaction_pointer_id)?; + match window.emit("tx_state_change", &transaction_pointer_id) { + Ok(_) => {} + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; Ok(()) } +// TODO - Handle fee remainder for deployment handler /// Handles updating deployment transaction and encrypted storage pub async fn handle_deployment_update_and_encrypted_storage( transaction_id: N::TransactionID, @@ -981,6 +1146,7 @@ pub async fn handle_deployment_update_and_encrypted_storage( ) -> AvailResult<()> { let backup = get_backup_flag()?; let sender_address = get_address::()?; + let view_key = VIEWSESSION.get_instance::()?; // Update transaction to pending to confirm let mut processing_deployment_pointer = get_deployment_pointer::(deployment_pointer_id)?; @@ -997,32 +1163,52 @@ pub async fn handle_deployment_update_and_encrypted_storage( )?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &deployment_pointer_id)?; + match window.emit("tx_state_change", &deployment_pointer_id) { + Ok(_) => {} + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; - let (block_height, _, _, transaction_state, rejected_tx_id, fee) = + let (block_height, transitions, _, transaction_state, _fee_tx_id, _, fee) = find_confirmed_block_height::(transaction_id)?; if transaction_state == TransactionState::Rejected { - processing_deployment_pointer.update_rejected_deployment( - "Transaction rejected by the Aleo blockchain.".to_string(), - rejected_tx_id, - block_height, - fee, - ); - - let updated_encrypted_deployment = - processing_deployment_pointer.to_encrypted_data(sender_address)?; + for transition in transitions { + input_spent_check(&transition, true)?; + transition_to_record_pointer( + transaction_id, + transition.clone(), + block_height, + view_key, + )?; + } - update_encrypted_transaction_state_by_id( + handle_deployment_rejection( + processing_deployment_pointer, deployment_pointer_id, - &updated_encrypted_deployment.ciphertext, - &updated_encrypted_deployment.nonce, - TransactionState::Rejected, + transaction_id, + block_height, + fee, + sender_address, )?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &deployment_pointer_id)?; + match window.emit("tx_state_change", &deployment_pointer_id) { + Ok(_) => {} + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; return Ok(()); @@ -1047,22 +1233,33 @@ pub async fn handle_deployment_update_and_encrypted_storage( )?; if let Some(window) = window.clone() { - window.emit("tx_state_change", &deployment_pointer_id)?; + match window.emit("tx_state_change", &deployment_pointer_id) { + Ok(_) => {} + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; }; return Ok(()); } - let mut pending_transaction_pointer = get_deployment_pointer::(deployment_pointer_id)?; - - pending_transaction_pointer.update_confirmed_deployment(transaction_id, block_height, fee); - let updated_encrypted_transaction = - pending_transaction_pointer.to_encrypted_data(sender_address)?; + //checking for remainder if private fee was spent + for transition in transitions { + input_spent_check(&transition, true)?; + transition_to_record_pointer(transaction_id, transition.clone(), block_height, view_key)?; + } - update_encrypted_data_by_id( + handle_deployment_confirmed( deployment_pointer_id, - &updated_encrypted_transaction.ciphertext, - &updated_encrypted_transaction.nonce, + transaction_id, + block_height, + fee, + sender_address, )?; // if record was spent on fee update state @@ -1075,13 +1272,165 @@ pub async fn handle_deployment_update_and_encrypted_storage( } if let Some(window) = window.clone() { - window.emit("tx_state_change", &deployment_pointer_id)?; + match window.emit("tx_state_change", &deployment_pointer_id) { + Ok(_) => {} + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; + }; + + Ok(()) +} + +pub fn handle_deployment_confirmed( + pointer_id: &str, + tx_id: N::TransactionID, + block_height: u32, + fee: Option, + sender_address: Address, +) -> AvailResult<()> { + let mut pending_transaction_pointer = get_deployment_pointer::(pointer_id)?; + + pending_transaction_pointer.update_confirmed_deployment(tx_id, block_height, fee); + let updated_encrypted_transaction = + pending_transaction_pointer.to_encrypted_data(sender_address)?; + + update_encrypted_data_by_id( + pointer_id, + &updated_encrypted_transaction.ciphertext, + &updated_encrypted_transaction.nonce, + )?; + + Ok(()) +} + +pub fn handle_deployment_rejection( + mut pointer: DeploymentPointer, + pointer_id: &str, + tx_id: N::TransactionID, + block_height: u32, + fee: Option, + sender_address: Address, +) -> AvailResult<()> { + pointer.update_rejected_deployment( + "Transaction rejected by the Aleo blockchain.".to_string(), + Some(tx_id), + block_height, + fee, + ); + + let updated_encrypted_deployment = pointer.to_encrypted_data(sender_address)?; + + update_encrypted_transaction_state_by_id( + pointer_id, + &updated_encrypted_deployment.ciphertext, + &updated_encrypted_deployment.nonce, + TransactionState::Rejected, + )?; + + Ok(()) +} + +pub fn handle_transaction_rejection( + mut pointer: TransactionPointer, + pointer_id: &str, + rejected_execution: Option>, + tx_id: Option, + block_height: u32, + fee: Option, + sender_address: Address, +) -> AvailResult<()> { + // NOTE - If bugs out change to update via input nonces + if let Some(rejected_execution) = rejected_execution { + let rejected_transitions = rejected_execution.transitions(); + + for transition in rejected_transitions { + if !transition.is_fee_private() { + input_spent_check::(transition, false)?; + } + } + } + + pointer.update_rejected_transaction( + "Transaction rejected by the Aleo blockchain.".to_string(), + tx_id, + block_height, + fee, + ); + + let updated_encrypted_transaction = pointer.to_encrypted_data(sender_address)?; + + update_encrypted_transaction_state_by_id( + pointer_id, + &updated_encrypted_transaction.ciphertext, + &updated_encrypted_transaction.nonce, + TransactionState::Rejected, + )?; + + Ok(()) +} + +pub fn handle_transaction_confirmed( + transaction_pointer_id: &str, + tx_id: N::TransactionID, + execution_transitions: Vec>, + block_height: u32, + timestamp: DateTime, + fee: Option, + sender_address: Address, +) -> AvailResult<()> { + let mut pending_transaction_pointer = get_transaction_pointer::(transaction_pointer_id)?; + + pending_transaction_pointer.update_confirmed_transaction( + tx_id, + block_height, + execution_transitions, + timestamp, + TransactionState::Confirmed, + fee, + ); + + let updated_encrypted_transaction = + pending_transaction_pointer.to_encrypted_data(sender_address)?; + + let program_ids = match updated_encrypted_transaction.clone().program_ids { + Some(program_ids) => program_ids, + None => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Program ids not found".to_string(), + "Program ids not found".to_string(), + )) + } + }; + + let function_ids = match updated_encrypted_transaction.clone().function_ids { + Some(function_ids) => function_ids, + None => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Function ids not found".to_string(), + "Function ids not found".to_string(), + )) + } }; + update_encrypted_transaction_confirmed_by_id( + transaction_pointer_id, + &updated_encrypted_transaction.ciphertext, + &updated_encrypted_transaction.nonce, + &program_ids, + &function_ids, + )?; + Ok(()) } -//TODO - Clean up check_inputs_outputs_inclusion /// Sync transaction whilst scanning blocks pub fn sync_transaction( transaction: &ConfirmedTransaction, @@ -1093,6 +1442,7 @@ pub fn sync_transaction( Option, Vec>, Vec, + bool, )> { let view_key = VIEWSESSION.get_instance::()?; let address = view_key.to_address(); @@ -1101,6 +1451,9 @@ pub fn sync_transaction( let mut encrypted_transition_pointers: Vec = vec![]; let mut execution_transitions: Vec> = vec![]; + let mut found_flag = false; + + let state = check_transaction_state::(transaction)?; for transition in transaction.transitions() { let ownership_check = match DecryptTransition::owns_transition( @@ -1113,7 +1466,7 @@ pub fn sync_transaction( }; if ownership_check { - input_spent_check(&transition.clone())?; + input_spent_check(&transition.clone(), true)?; let execution_transition = ExecutedTransition::::new( transition.program_id().to_string(), @@ -1149,6 +1502,8 @@ pub fn sync_transaction( if !transition.is_fee_private() && !transition.is_fee_public() { encrypted_transition_pointers.append(&mut encrypted_transitions); } + + found_flag = true; } } @@ -1171,11 +1526,12 @@ pub fn sync_transaction( let execution_tx = TransactionPointer::::new( None, Some(transaction.id().to_owned()), - TransactionState::Confirmed, + state, Some(block_height), None, None, execution_transitions, + vec![], timestamp, Some(timestamp), None, @@ -1188,6 +1544,8 @@ pub fn sync_transaction( let encrypted_exec_tx = execution_tx.to_encrypted_data(address)?; store_encrypted_data(encrypted_exec_tx.clone())?; + found_flag = true; + Some(encrypted_exec_tx) } false => None, @@ -1197,14 +1555,75 @@ pub fn sync_transaction( execution_transaction, record_pointers, encrypted_transition_pointers, + found_flag, )) } +pub fn check_transaction_state( + transaction: &ConfirmedTransaction, +) -> AvailResult { + if let ConfirmedTransaction::::AcceptedExecute(_, _, _) = transaction { + Ok(TransactionState::Confirmed) + } else if let ConfirmedTransaction::::AcceptedDeploy(_, _, _) = transaction { + Ok(TransactionState::Confirmed) + } else if let ConfirmedTransaction::::RejectedExecute(_, _, _, _) = transaction { + //TODO - Return rejected execution + + Ok(TransactionState::Rejected) + } else if let ConfirmedTransaction::::RejectedDeploy(_, _, _, _) = transaction { + Ok(TransactionState::Rejected) + } else { + Ok(TransactionState::Pending) + } +} + +pub fn get_executed_transitions( + transaction: &Transaction, + block_height: u32, +) -> AvailResult>> { + let view_key = VIEWSESSION.get_instance::()?; + let mut execution_transitions: Vec> = vec![]; + + for transition in transaction.transitions() { + input_spent_check(transition, true)?; + + let ownership_check = match DecryptTransition::owns_transition( + view_key, + *transition.tpk(), + *transition.tcm(), + ) { + Ok(res) => res, + Err(_e) => false, + }; + + if ownership_check { + let execution_transition = ExecutedTransition::::new( + transition.program_id().to_string(), + transition.function_name().to_string(), + transition.id().to_owned(), + ); + + if !transition.is_fee_private() && !transition.is_fee_public() { + execution_transitions.push(execution_transition); + } + + transition_to_record_pointer( + transaction.id(), + transition.clone(), + block_height, + view_key, + )?; + } + } + + Ok(execution_transitions) +} + pub fn get_fee_transition( transaction_id: N::TransactionID, ) -> AvailResult { let view_key = VIEWSESSION.get_instance::()?; - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let transaction = match api_client.get_transaction(transaction_id) { Ok(transaction) => transaction, @@ -1279,6 +1698,7 @@ pub fn to_commitment( pub fn parse_inputs( inputs: Vec, + function_identifier: &str, ) -> AvailResult<(Vec>, Vec, Option>, Option)> { // check if input is address let mut values: Vec> = vec![]; @@ -1302,15 +1722,18 @@ pub fn parse_inputs( values.push(value); } Err(_) => { - // value is constant plaintext input + let value = Value::from_str(input)?; - values.push(value); + values.push(value); + + // value is constant plaintext input + if function_identifier.contains("transfer") { - let trimmed_input = input.trim_end_matches("u64"); + let trimmed_input = input.trim_end_matches("u64"); - // TODO - Handle multiple u64s found - By seeing function name contains "transfer" - if let Ok(amount_found) = trimmed_input.parse::() { - amount = Some(amount_found as f64 / 1000000.0); + if let Ok(amount_found) = trimmed_input.parse::() { + amount = Some(amount_found as f64 / 1000000.0); + } } } } @@ -1395,92 +1818,96 @@ mod test { use super::*; use snarkvm::prelude::Testnet3; - #[tokio::test] - async fn test_token_record() { - let mut api_client = setup_local_client::(); - let pk = PrivateKey::::from_str(TESTNET_PRIVATE_KEY).unwrap(); - let pk_3 = PrivateKey::::from_str(TESTNET3_PRIVATE_KEY).unwrap(); - let vk = ViewKey::::try_from(pk).unwrap(); - let vk_3 = ViewKey::::try_from(pk_3).unwrap(); - let fee = 10000u64; - let program_id = "token_avl_4.aleo"; - // INPUTS - let address_to_mint = Value::::try_from(TESTNET3_ADDRESS).unwrap(); - let amt_input = Value::::try_from("100u64").unwrap(); - let transfer_amt = Value::::try_from("1u64").unwrap(); - let fee = 10000u64; - - // let token_program = Program::::from_str(TOKEN_PROGRAM).unwrap(); - let token_mint_program = Program::::from_str(TOKEN_MINT).unwrap(); - let mut program_manager = - ProgramManager::::new(Some(pk), None, Some(api_client.clone()), None) - .unwrap(); - let mut program_manager_3 = - ProgramManager::::new(Some(pk_3), None, Some(api_client.clone()), None) - .unwrap(); - // program_manager.add_program(&token_program); - program_manager.add_program(&token_mint_program); - program_manager_3.add_program(&token_mint_program); - - // STEP - 0 DEPLOY PROGRAM (DONT NEED TO DEPLOY AGAIN) - // let deployement_id = program_manager.deploy_program("token_avl_4.aleo", 10000u64, None, None).unwrap(); - // println!("----> Program Deployed - {:?}", deployement_id); - // let mint_program: Result, Command>, snarkvm::prelude::Error> = api_client.get_program("token_avl.aleo"); - - // STEP - 1 MINT ****ONLY FOR TESTING PURPOSES**** - // let inputs = vec![address_to_mint.clone(), amt_input.clone()]; - // let mint_tokens = program_manager.execute_program(program_id, "mint_public", inputs.iter(), fee, None, None).unwrap().to_string(); - // println!("----> Tokens Minted - {:?}", mint_tokens); - - // let mint_txn = api_client.get_transaction(::TransactionID::from_str("at17hlupnq8nutyzvdccj5smhf6s8u7yzplwjf38xzqgl93486r3c8s9mrhuc").unwrap()).unwrap(); - // println!("----> Mint Tokens TXN - {:?}", mint_txn); - - // STEP - 2 QUERY MAPPING TO VERIFY - // let mapping_op = program_manager.get_mapping_value(program_id, "account", TESTNET3_ADDRESS).unwrap(); - // println!("----> Mapping Value - {:?}", mapping_op); - - // STEP - 4 PREPARE TOKEN RECORD BY USING transfer_public_to_private() fn - // let inputs = vec![address_to_mint.clone(), transfer_amt.clone()]; - // let token_record = program_manager_3.execute_program(program_id, "transfer_public_to_private", inputs.iter(),fee, None, None).unwrap().to_string(); - // let record_txn = api_client.get_transaction(::TransactionID::from_str(&token_record).unwrap()).unwrap(); - - // println!("----> Token Record TXN - {:?}", record_txn); - - // let record_txn_id = ::TransactionID::from_str("at18r5vumc27swqw0vtm9gp4la0cwg8nxk4njm49sp2dj7anp596c9qgaz66w").unwrap(); - // let record_txn = api_client.get_transaction(::TransactionID::from_str("at18r5vumc27swqw0vtm9gp4la0cwg8nxk4njm49sp2dj7anp596c9qgaz66w").unwrap()).unwrap(); - // // println!("----> Token Record TXN - {:?}", record_txn); - // let mut latest_height = api_client.latest_height().unwrap(); - // for transition in record_txn.clone().into_transitions(){ - // println!("INN"); - // if transition.program_id().to_string() == program_id { - // println!("OKK"); - // let record_pointer_token = transition_to_record_pointer::(record_txn.clone().id(), transition.clone(), latest_height, vk_3).unwrap(); - - // println!("----> Token Record - {:?}", record_pointer_token); - // } - // } - - // STEP - 4 QUERY LOCAL STORAGE TO VERIFY - let mapping_op = program_manager - .get_mapping_value(program_id, "account", TESTNET3_ADDRESS) - .unwrap(); - println!("----> Mapping Value - {:?}", mapping_op); - let local_db_value = get_balance("token_avl_4.record", vk_3).unwrap(); - println!("----> Local DB Value - {:?}", local_db_value); + async fn test_get_all_nft_data() { + let res = get_all_nft_data::().unwrap(); + println!("res\n {:?}", res); } + // #[tokio::test] + // async fn test_token_record() { + // let mut api_client = setup_client::().unwrap(); + // let pk = PrivateKey::::from_str(TESTNET_PRIVATE_KEY).unwrap(); + // let pk_3 = PrivateKey::::from_str(TESTNET3_PRIVATE_KEY).unwrap(); + // let vk = ViewKey::::try_from(pk).unwrap(); + // let vk_3 = ViewKey::::try_from(pk_3).unwrap(); + // let fee = 10000u64; + // let program_id = "token_avl_4.aleo"; + // // INPUTS + // let address_to_mint = Value::::try_from(TESTNET3_ADDRESS).unwrap(); + // let amt_input = Value::::try_from("100u64").unwrap(); + // let transfer_amt = Value::::try_from("1u64").unwrap(); + // let fee = 10000u64; - #[test] - fn test_get_private_balance() { - let _res = get_private_token_balance::("credits").unwrap(); + // // let token_program = Program::::from_str(TOKEN_PROGRAM).unwrap(); + // let token_mint_program = Program::::from_str(TOKEN_MINT).unwrap(); + // let mut program_manager = + // ProgramManager::::new(Some(pk), None, Some(api_client.clone()), None) + // .unwrap(); + // let mut program_manager_3 = + // ProgramManager::::new(Some(pk_3), None, Some(api_client.clone()), None) + // .unwrap(); + // // program_manager.add_program(&token_program); + // program_manager.add_program(&token_mint_program); + // program_manager_3.add_program(&token_mint_program); + + // STEP - 0 DEPLOY PROGRAM (DONT NEED TO DEPLOY AGAIN) + // let deployement_id = program_manager.deploy_program("token_avl_4.aleo", 10000u64, None, None).unwrap(); + // println!("----> Program Deployed - {:?}", deployement_id); + // let mint_program: Result, Command>, snarkvm::prelude::Error> = api_client.get_program("token_avl.aleo"); + + // STEP - 1 MINT ****ONLY FOR TESTING PURPOSES**** + // let inputs = vec![address_to_mint.clone(), amt_input.clone()]; + // let mint_tokens = program_manager.execute_program(program_id, "mint_public", inputs.iter(), fee, None, None).unwrap().to_string(); + // println!("----> Tokens Minted - {:?}", mint_tokens); + + // let mint_txn = api_client.get_transaction(::TransactionID::from_str("at17hlupnq8nutyzvdccj5smhf6s8u7yzplwjf38xzqgl93486r3c8s9mrhuc").unwrap()).unwrap(); + // println!("----> Mint Tokens TXN - {:?}", mint_txn); + + // STEP - 2 QUERY MAPPING TO VERIFY + // let mapping_op = program_manager.get_mapping_value(program_id, "account", TESTNET3_ADDRESS).unwrap(); + // println!("----> Mapping Value - {:?}", mapping_op); + + // STEP - 4 PREPARE TOKEN RECORD BY USING transfer_public_to_private() fn + // let inputs = vec![address_to_mint.clone(), transfer_amt.clone()]; + // let token_record = program_manager_3.execute_program(program_id, "transfer_public_to_private", inputs.iter(),fee, None, None).unwrap().to_string(); + // let record_txn = api_client.get_transaction(::TransactionID::from_str(&token_record).unwrap()).unwrap(); + + // println!("----> Token Record TXN - {:?}", record_txn); + + // let record_txn_id = ::TransactionID::from_str("at18r5vumc27swqw0vtm9gp4la0cwg8nxk4njm49sp2dj7anp596c9qgaz66w").unwrap(); + // let record_txn = api_client.get_transaction(::TransactionID::from_str("at18r5vumc27swqw0vtm9gp4la0cwg8nxk4njm49sp2dj7anp596c9qgaz66w").unwrap()).unwrap(); + // // println!("----> Token Record TXN - {:?}", record_txn); + // let mut latest_height = api_client.latest_height().unwrap(); + // for transition in record_txn.clone().into_transitions(){ + // println!("INN"); + // if transition.program_id().to_string() == program_id { + // println!("OKK"); + // let record_pointer_token = transition_to_record_pointer::(record_txn.clone().id(), transition.clone(), latest_height, vk_3).unwrap(); + + // println!("----> Token Record - {:?}", record_pointer_token); + // } + // } - println!("res: {:?}", _res); - } + // STEP - 4 QUERY LOCAL STORAGE TO VERIFY + // let mapping_op = program_manager + // .get_mapping_value(program_id, "account", TESTNET3_ADDRESS) + // .unwrap(); + // println!("----> Mapping Value - {:?}", mapping_op); + // let local_db_value = get_balance("token_avl_4.record", vk_3).unwrap(); + // println!("----> Local DB Value - {:?}", local_db_value); + // } - #[test] - fn get_public_balance() { - get_public_token_balance::("credits").unwrap(); - } + // #[test] + // fn test_get_private_balance() { + // let _res = get_private_token_balance::("credits").unwrap(); + + // println!("res: {:?}", _res); + // } + + // #[test] + // fn get_public_balance() { + // get_public_token_balance::("credits").unwrap(); + // } // #[tokio::test] // async fn test_estimate_fee() { @@ -1497,7 +1924,7 @@ mod test { // //let inputs = vec![]; - // let api_client = setup_local_client::(); + // let api_client = setup_client::().unwrap(); // let pk = PrivateKey::::from_str(TESTNET_PRIVATE_KEY).unwrap(); // let program_manager = @@ -1514,7 +1941,7 @@ mod test { // #[tokio::test] // async fn test_nft_record(){ // // ARRANGE - // let mut api_client = setup_local_client::(); + // let mut api_client = setup_client::().unwrap(); // // ALEO INPUTS // let program_id = "avail_nft_0.aleo"; // let nft_program = Program::::from_str(AVAIL_NFT_TEST).unwrap(); diff --git a/backend/src/services/records.rs b/backend/src/services/records.rs new file mode 100644 index 00000000..b8f67a43 --- /dev/null +++ b/backend/src/services/records.rs @@ -0,0 +1,127 @@ +// Multi Threaded get_records - in construction FIX: Output not matching +//gets the records form the latest aleo blocks and forms and returns a vector of our local block type +/* +fn get_nova_records(last_sync: u32) -> AvailResult<(Vec, Vec)> { + let view_key = get_view_session::()?; + + let api_client = AleoAPIClient::::local_testnet3("3030"); + let chunk_size = 49; + + let latest_height = api_client.latest_height()?; + + let total_blocks_num = latest_height.sub(last_sync); + let num_chunks = total_blocks_num / chunk_size; + + let chunk_ranges: Vec<(u32, u32)> = (0..num_chunks) + .map(|chunk_index| { + let start_block = chunk_index * chunk_size; + let end_block = (start_block + chunk_size).min(total_blocks_num); + (start_block, end_block) + }) + .collect(); + + let synced_encrypted_blocks = get_encrypted_data_by_flavour(EncryptedDataTypeCommon::Block)? + .iter() + .map(|data| Ok(deserialize(&data.data)?)) + .collect::, AvError>>()?; + + //decrypt and form a vector of block heights + let synced_blocks = synced_encrypted_blocks + .iter() + .map(|block| Block::decrypt(block.to_owned()).unwrap()) + .map(|block| block.block_height) + .collect::>(); + + let address_x_coordinate = view_key.to_address().to_x_coordinate(); + + let sk_tag = GraphKey::try_from(view_key)?.sk_tag(); + + let mut end_height = latest_height; + let mut start_height = latest_height.sub(step_size); + + let mut tags: Vec = vec![]; + let mut new_record_blocks: Vec = vec![]; + + for _ in (last_sync..latest_height).step_by(step_size as usize) { + println!("start_height: {:?}", start_height); + println!("end_height: {:?}", end_height); + let blocks = api_client.get_blocks(start_height, end_height)?; + + let blocks = match blocks { + Ok(res) => res, + Err(_) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error getting blocks".to_string(), + "".to_string(), + )); + } + }; + + tags.append(&mut current_tags); + + //remove any blocks that are already synced + let blocks = blocks + .clone() + .into_iter() + .filter(|block| !synced_blocks.contains(&block.height())) + .collect::>>(); + + let mut optimized_blocks = blocks + .iter() + .map(|block| { + let records = block.clone().into_records(); + let height = block.height(); + + let identifiers = records + .into_iter() + .filter(|(_, record)| { + record.is_owner_with_address_x_coordinate(&view_key, &address_x_coordinate) + }) + .filter_map(|(commitment, record)| { + let record = record.decrypt(&view_key).ok()?; + let tag = Record::>::tag(sk_tag, commitment).ok()?; + let amount = record.microcredits().ok()?; + if amount == 0 { + None + } else { + Some(Identifiers::new( + commitment.to_string(), + tag.to_string(), + amount, + "unspent".to_string(), + )) + } + }) + .collect(); + + Block::new(height, identifiers) + }) + .collect::>(); + + // Search in reverse order from the latest block to the earliest block + end_height = start_height; + start_height = start_height.saturating_sub(step_size); + if start_height < last_sync { + start_height = last_sync + }; + + Ok(avail_blocks) + }) + + + let tags = tags.into_inner(); + + let tags = match tags { + Ok(res) => res, + Err(_) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Mutex error ".to_string(), + "".to_string(), + )); + } + }; + + Ok((new_record_blocks, tags)) +}*/ diff --git a/backend/src/services/wallet_connect_api.rs b/backend/src/services/wallet_connect_api.rs index 0319fa57..0cfd0fda 100644 --- a/backend/src/services/wallet_connect_api.rs +++ b/backend/src/services/wallet_connect_api.rs @@ -8,7 +8,10 @@ use super::{ get_avail_event_raw, get_avail_events_raw, get_event_raw, get_events_raw, get_succinct_avail_event_raw, get_succinct_avail_events_raw, }, - records::{get_page_count_for_filter, get_record_pointers, update_record_spent_local}, + records::{ + get_page_count_for_filter, get_record_pointers, update_record_spent_local, + update_record_spent_local_via_nonce, + }, }, utils::{get_private_key, sign_message}, }, @@ -24,7 +27,7 @@ use super::{ use chrono::Local; use std::str::FromStr; -use crate::api::aleo_client::setup_local_client; +use crate::api::aleo_client::setup_client; use crate::models::event::{AvailEvent, SuccinctAvailEvent}; use crate::models::pointers::{deployment::DeploymentPointer, transaction::TransactionPointer}; use crate::models::wallet_connect::{ @@ -39,7 +42,7 @@ use crate::models::wallet_connect::{ use snarkvm::circuit::Aleo; use snarkvm::{ circuit::{AleoV0, Environment}, - prelude::{Ciphertext, Network, Program, Record, Testnet3}, + prelude::{Ciphertext, Network, Program, Record, Testnet3,Signature,Address,Field}, }; use tauri::{Manager, Window}; @@ -51,18 +54,19 @@ use avail_common::{ encrypted_data::{EventTypeCommon, TransactionState}, network::SupportedNetworks, }, + converters::messages::{utf8_string_to_bits,field_to_fields} }; #[tauri::command(rename_all = "snake_case")] pub fn get_balance(request: BalanceRequest) -> AvailResult { let network = get_network()?; - + println!("===> Asset ID in Request Backend {:?}", Some(request.asset_id())); //TODO - Read ARC20 to deduce assets id something like {program_id/record_name} seems reasonable. let asset_id = match request.asset_id() { Some(asset_id) => asset_id, None => "credits".to_string(), }; - + println!("===> Asset ID in Backend {:?}", asset_id); //TODO - V2 HD wallet support let _address = match request.address() { Some(address) => address.to_string(), @@ -97,7 +101,7 @@ pub async fn request_create_event_raw, ) -> AvailResult { - let api_client = setup_local_client::(); + let api_client = setup_client::()?; let private_key = match get_private_key::(None) { Ok(private_key) => { PASS.extend_session()?; @@ -106,7 +110,16 @@ pub async fn request_create_event_raw match e.error_type { AvailErrorType::Unauthorized => { if let Some(window) = window { - window.emit("reauthenticate", "create-event")?; + match window.emit("reauthenticate", "create-event"){ + Ok(_) => {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting reauthentication event".to_string(), + "Error emitting reauthentication state".to_string(), + )); + } + }; } return Ok(CreateEventResponse::new( @@ -129,24 +142,9 @@ pub async fn request_create_event_raw::new(Some(private_key), None, Some(api_client), None)?; - if request.event_type() == &EventTypeCommon::Deploy { - let mut pending_deployment_tx = DeploymentPointer::::new( - None, - request.program_id().clone(), - request.fee(), - TransactionState::Processing, - None, - Local::now(), - None, - None, - ); - - let pending_event_id = pending_deployment_tx.encrypt_and_store(address)?; - - if let Some(window) = window.clone() { - window.emit("tx_state_change", &pending_event_id)?; - } + let mut fee_record_nonce: Option = None; + if request.event_type() == &EventTypeCommon::Deploy { let program = match request.inputs().get(0) { Some(program) => program, None => { @@ -182,11 +180,46 @@ pub async fn request_create_event_raw { let (fee_record, fee_commitment, fee_id) = find_aleo_credits_record_to_spend::(&fee, vec![])?; + + let fee_nonce = fee_record.nonce().to_string(); + fee_record_nonce = Some(fee_nonce); + (Some(fee_record), Some(fee_commitment), Some(fee_id)) } false => (None, None, None), }; + let mut pending_deployment_tx = DeploymentPointer::::new( + None, + request.program_id().clone(), + request.fee(), + TransactionState::Processing, + None, + fee_record_nonce, + Local::now(), + None, + None, + ); + + let pending_event_id = pending_deployment_tx.encrypt_and_store(address)?; + + if let Some(window) = window.clone() { + match window.emit("tx_state_change", &pending_event_id){ + Ok(_) => {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; + } + + if let Some(fee_id) = fee_id.clone() { + update_record_spent_local::(&fee_id, true)?; + } + let transaction_id = match program_manager.deploy_program(program.id(), 0, fee_record, None) { Ok(tx_id) => tx_id, @@ -210,14 +243,23 @@ pub async fn request_create_event_raw {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; } return Ok(CreateEventResponse::new( Some(pending_event_id), Some(format!( "Error deploying program: '{}'", - program.id().to_string() + program.id() )), )); } @@ -233,18 +275,26 @@ pub async fn request_create_event_raw = vec![]; + let (input_values, input_nonces, recipient_address, amount) = - parse_inputs::(request.inputs().clone())?; + parse_inputs::(request.inputs().clone(),&request.function_id().clone())?; let (fee_record, _fee_commitment, fee_id) = match fee_private { true => { let (fee_record, fee_commitment, fee_id) = - find_aleo_credits_record_to_spend::(&fee, input_nonces)?; + find_aleo_credits_record_to_spend::(&fee, input_nonces.clone())?; + + let fee_nonce = fee_record.nonce().to_string(); + record_nonces.push(fee_nonce); + (Some(fee_record), Some(fee_commitment), Some(fee_id)) } false => (None, None, None), }; + record_nonces.extend(input_nonces.clone()); + let mut pending_transaction = TransactionPointer::::new( None, None, @@ -253,6 +303,7 @@ pub async fn request_create_event_raw {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; } + // TODO - Update fee to spent and input_nonces to spent + for nonce in input_nonces.clone() { + update_record_spent_local_via_nonce::(&nonce, true)?; + } + + if let Some(fee_id) = fee_id.clone() { + update_record_spent_local::(&fee_id, false)?; + } + println!("=====> INPUTS {:?}", input_values); let transaction_id = match program_manager.execute_program( request.program_id().clone(), request.function_id().clone(), @@ -282,6 +351,10 @@ pub async fn request_create_event_raw(&fee_id, false)?; } + for nonce in input_nonces { + update_record_spent_local_via_nonce::(&nonce, false)?; + } + pending_transaction.update_failed_transaction( "Transaction execution failed, no records were spent.".to_string(), ); @@ -297,7 +370,16 @@ pub async fn request_create_event_raw {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting tx_state_change event".to_string(), + "Error emitting transaction state".to_string(), + )); + } + }; } return Ok(CreateEventResponse::new( @@ -399,7 +481,16 @@ pub fn sign(request: SignatureRequest, window: Window) -> AvailResult { if e.error_type == AvailErrorType::Unauthorized { - window.emit("reauthenticate", "sign")?; + match window.emit("reauthenticate", "sign"){ + Ok(_) => {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting reauthentication event".to_string(), + "Error emitting reauthentication state".to_string(), + )); + } + }; } Ok(SignatureResponse::new( None, @@ -417,7 +508,16 @@ pub fn sign(request: SignatureRequest, window: Window) -> AvailResult { if e.error_type == AvailErrorType::Unauthorized { - window.emit("reauthenticate", "sign")?; + match window.emit("reauthenticate", "sign"){ + Ok(_) => {}, + Err(e) => { + return Err(AvailError::new( + AvailErrorType::Internal, + "Error emitting reauthentication event".to_string(), + "Error emitting reauthentication state".to_string(), + )); + } + }; } Ok(SignatureResponse::new( None, @@ -430,6 +530,29 @@ pub fn sign(request: SignatureRequest, window: Window) -> AvailResult AvailResult{ + let network = get_network()?; + + match SupportedNetworks::from_str(&network)? { + SupportedNetworks::Testnet3 => verify_signature::(message, address, signature), + _ => verify_signature::(message, address, signature), + } +} + +fn verify_signature(message: &str, address:&str, signature: &str) -> AvailResult{ + let signature = Signature::::from_str(signature)?; + let address = Address::::from_str(address)?; + + let msg_bits = utf8_string_to_bits(message); + let msg_field = N::hash_bhp512(&msg_bits)?; + let msg = field_to_fields(&msg_field)?; + + let result = signature.verify(&address, &msg); + + Ok(result) +} + #[tauri::command(rename_all = "snake_case")] pub fn decrypt_records(request: DecryptRequest) -> AvailResult { let network = get_network()?; @@ -573,6 +696,7 @@ mod test { get_encrypted_data_by_flavour, initialize_encrypted_data_table, }; use crate::services::local_storage::persistent_storage::initial_user_preferences; + use crate::services::local_storage::utils::sign_message_w_key; use crate::services::local_storage::{ encrypted_data::drop_encrypted_data_table, persistent_storage::delete_user_preferences, }; @@ -1048,6 +1172,22 @@ mod test { println!("Result: {:?}", res); } + #[test] + fn test_verify_signature() { + let pk = PrivateKey::::from_str(TESTNET_PRIVATE_KEY).unwrap(); + + let message = "Hello World"; + + let (signature,_) = sign_message_w_key::(message, &pk).unwrap(); + + let address = Address::::try_from(&pk).unwrap(); + + + let res = verify(message, &address.to_string(), &signature.to_string()).unwrap(); + + assert_eq!(res, true); + } + #[test] fn test_fee_f64() { let fee = 0.3; diff --git a/backend/tauri.conf.json b/backend/tauri.conf.json index 373c90f6..3a433a2f 100644 --- a/backend/tauri.conf.json +++ b/backend/tauri.conf.json @@ -7,17 +7,10 @@ "withGlobalTauri": true }, "package": { - "productName": "Avail Wallet" + "productName": "Avail", + "version": "0.0.1" }, "tauri": { - "updater": { - "active": true, - "endpoints": [ - "https://api.avail.global/release/{target}/{arch}/{current_version}" - ], - "dialog": true, - "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFBM0E5NTJEMTAyMzg5NApSV1NVT0FMUlVxbWpBUWt1aXREWmJBR0g2UU9ZZEk3T0FINy9TVERLS1dtM2RITm5LVkNySXByYwo==" - }, "bundle": { "active": true, "icon": [ @@ -27,9 +20,13 @@ "icons/icon.icns", "icons/icon.ico" ], - "category": "Finance", "identifier": "com.avail.wallet", - "targets": "all" + "targets": "all", + "macOS": { + "signingIdentity": "WTK34UZPZK", + "entitlements": "/Users/zk/Avail/Apple/plists/entitlements.plist", + "exceptionDomain": "" + } }, "security": { "csp": null @@ -46,4 +43,4 @@ } ] } -} +} \ No newline at end of file