From 5ddacf524c9f24f4ba815b9c8fd7d6beb89cbbe3 Mon Sep 17 00:00:00 2001 From: Hanh Date: Tue, 15 Nov 2022 18:21:47 +0800 Subject: [PATCH] wip --- Cargo.toml | 2 +- binding.h | 2 + src/api/account.rs | 46 +++++++---- src/api/dart_ffi.rs | 19 ++++- src/api/payment.rs | 136 --------------------------------- src/api/payment_v2.rs | 21 +++-- src/api/recipient.rs | 3 + src/api/sync.rs | 45 +++++------ src/coinconfig.rs | 27 ++++++- src/db.rs | 134 +++++++++++++++++++++----------- src/db/migration.rs | 11 ++- src/main/rpc.rs | 9 ++- src/note_selection/builder.rs | 18 +++-- src/note_selection/optimize.rs | 3 + src/note_selection/ser.rs | 2 +- src/note_selection/tests.rs | 5 ++ src/note_selection/types.rs | 4 + src/note_selection/utxo.rs | 6 +- src/orchard/hash.rs | 3 +- src/orchard/note.rs | 10 ++- src/scan.rs | 39 +++++++++- src/sync.rs | 15 ++++ src/sync/trial_decrypt.rs | 6 +- src/transaction.rs | 63 ++++++++++----- src/ua.rs | 88 --------------------- 25 files changed, 345 insertions(+), 372 deletions(-) delete mode 100644 src/api/payment.rs delete mode 100644 src/ua.rs diff --git a/Cargo.toml b/Cargo.toml index e71470d..12936c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ prost = "0.10.3" rayon = "1.5.1" tiny-bip39 = "0.8" rand = "0.8.4" -rusqlite = { version = "0.27.0", features = ["bundled"] } +rusqlite = { version = "0.27.0", features = ["bundled", "modern_sqlite"] } jubjub = "0.9.0" bls12_381 = "0.7" ff = "0.12" diff --git a/binding.h b/binding.h index 0dc1735..54c2186 100644 --- a/binding.h +++ b/binding.h @@ -50,6 +50,8 @@ void deallocate_str(char *s); void init_wallet(char *db_path); +void migrate_db(char *db_path); + void set_active(uint8_t active); void set_active_account(uint8_t coin, uint32_t id); diff --git a/src/api/account.rs b/src/api/account.rs index 3b1f9d6..954d3c2 100644 --- a/src/api/account.rs +++ b/src/api/account.rs @@ -79,19 +79,22 @@ fn new_account_with_key(coin: u8, name: &str, key: &str, index: u32) -> anyhow:: let c = CoinConfig::get(coin); let (seed, sk, ivk, pa) = decode_key(coin, key, index)?; let db = c.db()?; - let (account, exists) = - db.store_account(name, seed.as_deref(), index, sk.as_deref(), &ivk, &pa)?; - if !exists { - if c.chain.has_transparent() { - db.create_taddr(account)?; + let account = db.get_account_id(&ivk)?; + let account = match account { + Some(account) => account, + None => { + let account = + db.store_account(name, seed.as_deref(), index, sk.as_deref(), &ivk, &pa)?; + if c.chain.has_transparent() { + db.create_taddr(account)?; + } + if c.chain.has_unified() { + db.create_orchard(account)?; + } + db.store_ua_settings(account, false, true, c.chain.has_unified())?; + account } - if c.chain.has_unified() { - db.create_orchard(account)?; - } - db.store_ua_settings(account, true, true, true)?; - } else { - db.store_ua_settings(account, false, true, false)?; - } + }; Ok(account) } @@ -319,13 +322,24 @@ pub fn get_unified_address( Ok(address) } +fn get_sapling_address(coin: u8, id_account: u32) -> anyhow::Result { + let c = CoinConfig::get(coin); + let db = c.db()?; + let AccountData { address, .. } = db.get_account_info(id_account)?; + Ok(address) +} + pub fn get_address(coin: u8, id_account: u32, address_type: u8) -> anyhow::Result { let c = CoinConfig::get(coin); - let t = address_type & 1 != 0; - let s = address_type & 2 != 0; - let o = address_type & 4 != 0; + let address = if c.chain.has_unified() { + let t = address_type & 1 != 0; + let s = address_type & 2 != 0; + let o = address_type & 4 != 0; - let address = get_unified_address(coin, id_account, t, s, o)?; + get_unified_address(coin, id_account, t, s, o)? + } else { + get_sapling_address(coin, id_account)? + }; Ok(address) } diff --git a/src/api/dart_ffi.rs b/src/api/dart_ffi.rs index aef0f81..a17a662 100644 --- a/src/api/dart_ffi.rs +++ b/src/api/dart_ffi.rs @@ -1,4 +1,4 @@ -use crate::coinconfig::{init_coin, CoinConfig}; +use crate::coinconfig::{init_coin, migrate_coin, CoinConfig}; use crate::note_selection::TransactionReport; use crate::{ChainError, TransactionPlan, Tx}; use allo_isolate::{ffi, IntoDart}; @@ -95,13 +95,24 @@ pub struct CResult { error: *mut c_char, } +const COIN_DBNAMES: &[&str] = &["zec.db", "yec.db"]; + #[no_mangle] pub unsafe extern "C" fn init_wallet(db_path: *mut c_char) { try_init_logger(); from_c_str!(db_path); - let _ = init_coin(0, &format!("{}/zec-test.db", &db_path)); - let _ = init_coin(1, &format!("{}/yec.db", &db_path)); - let _ = init_coin(2, &format!("{}/arrr.db", &db_path)); + for (coin, filename) in COIN_DBNAMES.iter().enumerate() { + let _ = init_coin(coin as u8, &format!("{}/{}", &db_path, filename)); + } +} + +#[no_mangle] +pub unsafe extern "C" fn migrate_db(db_path: *mut c_char) { + try_init_logger(); + from_c_str!(db_path); + for (coin, filename) in COIN_DBNAMES.iter().enumerate() { + let _ = migrate_coin(coin as u8, &format!("{}/{}", &db_path, filename)); + } } #[no_mangle] diff --git a/src/api/payment.rs b/src/api/payment.rs deleted file mode 100644 index 94baebb..0000000 --- a/src/api/payment.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! Payments - -use anyhow::anyhow; -use std::str::FromStr; - -use secp256k1::SecretKey; - -use crate::api::sync::get_latest_height; -use crate::coinconfig::{get_prover, CoinConfig}; -use crate::pay::TxBuilder; -pub use crate::{broadcast_tx, Tx}; -use zcash_client_backend::encoding::{ - decode_extended_full_viewing_key, decode_extended_spending_key, -}; -use zcash_primitives::consensus::Parameters; -use zcash_primitives::transaction::builder::Progress; - -use crate::db::{AccountData, ZMessage}; -use crate::taddr::get_utxos; -use serde::Deserialize; -use zcash_primitives::memo::Memo; -// use crate::wallet::Recipient; - -type PaymentProgressCallback = Box; - -async fn prepare_multi_payment( - last_height: u32, - recipients: &[RecipientMemo], - anchor_offset: u32, -) -> anyhow::Result<(Tx, Vec)> { - let c = CoinConfig::get_active(); - let change_address = c.db()?.get_account_change_address(c.id_account)?; - // let mut tx_builder = TxBuilder::new(c.coin_type, last_height); - // - // let fvk = decode_extended_full_viewing_key( - // c.chain.network().hrp_sapling_extended_full_viewing_key(), - // &fvk, - // ) - // .unwrap(); - // let utxos = if use_transparent { - // let mut client = c.connect_lwd().await?; - // let t_address = c.db()?.get_taddr(c.id_account)?; - // if let Some(t_address) = t_address { - // get_utxos(&mut client, &t_address, c.id_account).await? - // } else { - // vec![] - // } - // } else { - // vec![] - // }; - // - // let target_amount: u64 = recipients.iter().map(|r| r.amount).sum(); - // let anchor_height = last_height.saturating_sub(anchor_offset); - // let spendable_notes = c - // .db()? - // .get_spendable_notes(c.id_account, anchor_height, &fvk)?; - // let note_ids = tx_builder.select_inputs(&fvk, &spendable_notes, &utxos, target_amount)?; - // tx_builder.select_outputs(&fvk, recipients)?; - // Ok((tx_builder.tx, note_ids)) - todo!() -} - -fn sign(tx: &Tx, progress_callback: PaymentProgressCallback) -> anyhow::Result> { - // let c = CoinConfig::get_active(); - // let prover = get_prover(); - // let db = c.db()?; - // let AccountData { sk: zsk, .. } = db.get_account_info(c.id_account)?; - // let zsk = zsk.ok_or(anyhow!("Cannot sign without secret key"))?; - // let tsk = db - // .get_tsk(c.id_account)? - // .map(|tsk| SecretKey::from_str(&tsk).unwrap()); - // let extsk = - // decode_extended_spending_key(c.chain.network().hrp_sapling_extended_spending_key(), &zsk) - // .unwrap(); - // let raw_tx = tx.sign(tsk, &extsk, prover, progress_callback)?; - // Ok(raw_tx) - todo!() -} - -/// Build a multi payment for offline signing -/// # Arguments -/// * `last_height`: current block height -/// * `recipients`: list of recipients -/// * `use_transparent`: include transparent balance -/// * `anchor_offset`: minimum number of confirmations for note selection -pub async fn build_only_multi_payment( - last_height: u32, - recipients: &[RecipientMemo], - use_transparent: bool, - anchor_offset: u32, -) -> anyhow::Result { - let (tx, _) = - prepare_multi_payment(last_height, recipients, use_transparent, anchor_offset).await?; - // let tx_str = serde_json::to_string(&tx)?; - Ok(tx) -} - -/// Sign a transaction -/// # Arguments -/// * `tx`: transaction to sign -/// * `progress_callback`: function callback during transaction building -pub async fn sign_only_multi_payment( - tx: &Tx, - progress_callback: PaymentProgressCallback, -) -> anyhow::Result> { - // let tx = serde_json::from_str::(tx_string)?; - let raw_tx = sign(tx, progress_callback)?; - Ok(raw_tx) -} - -/// Build, sign and broadcast a multi payment -/// # Arguments -/// * `last_height`: current block height -/// * `recipients`: list of recipients -/// * `use_transparent`: include transparent balance -/// * `anchor_offset`: minimum number of confirmations for note selection -/// * `progress_callback`: function callback during transaction building -pub async fn build_sign_send_multi_payment( - last_height: u32, - recipients: &[RecipientMemo], - use_transparent: bool, - anchor_offset: u32, - progress_callback: PaymentProgressCallback, -) -> anyhow::Result { - let c = CoinConfig::get_active(); - let (tx, note_ids) = - prepare_multi_payment(last_height, recipients, anchor_offset).await?; - let raw_tx = sign(&tx, progress_callback)?; - let tx_id = broadcast_tx(&raw_tx).await?; - - c.db()?.tx_mark_spend(¬e_ids)?; - let mut mempool = c.mempool.lock().unwrap(); - mempool.clear()?; - Ok(tx_id) -} - diff --git a/src/api/payment_v2.rs b/src/api/payment_v2.rs index ec4ef50..ae0f8b1 100644 --- a/src/api/payment_v2.rs +++ b/src/api/payment_v2.rs @@ -2,7 +2,7 @@ use crate::api::account::get_unified_address; use crate::api::recipient::{RecipientMemo, RecipientShort}; use crate::api::sync::get_latest_height; pub use crate::broadcast_tx; -use crate::note_selection::{FeeZIP327, Order, TransactionReport}; +use crate::note_selection::{FeeFlat, FeeZIP327, Order, TransactionReport}; use crate::{ build_tx, fetch_utxos, get_secret_keys, AccountData, CoinConfig, TransactionBuilderConfig, TransactionPlan, TxBuilderContext, @@ -22,12 +22,17 @@ pub async fn build_tx_plan( confirmations: u32, ) -> anyhow::Result { let c = CoinConfig::get(coin); - let fvk = { + let (fvk, checkpoint_height) = { let db = c.db()?; let AccountData { fvk, .. } = db.get_account_info(account)?; - fvk + let anchor_height = last_height.saturating_sub(confirmations); + let checkpoint_height = db + .get_checkpoint_height(anchor_height)? + .unwrap_or_else(|| db.sapling_activation_height()); // get the latest checkpoint before the requested anchor height + (fvk, checkpoint_height) }; let change_address = get_unified_address(coin, account, true, true, true)?; + let context = TxBuilderContext::from_height(coin, checkpoint_height)?; let mut orders = vec![]; let mut id_order = 0; @@ -47,14 +52,15 @@ pub async fn build_tx_plan( id_order += 1; } } - let utxos = fetch_utxos(coin, account, last_height, true, confirmations).await?; + let utxos = fetch_utxos(coin, account, checkpoint_height, true).await?; log::info!("UTXO: {:?}", utxos); let config = TransactionBuilderConfig::new(&change_address); - let tx_plan = crate::note_selection::build_tx_plan::( + let tx_plan = crate::note_selection::build_tx_plan::( &fvk, - last_height, + checkpoint_height, + &context.orchard_anchor, &utxos, &orders, &config, @@ -75,8 +81,7 @@ pub fn sign_plan(coin: u8, account: u32, tx_plan: &TransactionPlan) -> anyhow::R } let keys = get_secret_keys(coin, account)?; - let context = TxBuilderContext::from_height(c.coin, tx_plan.height)?; - let tx = build_tx(c.chain.network(), &keys, &tx_plan, context, OsRng).unwrap(); + let tx = build_tx(c.chain.network(), &keys, &tx_plan, OsRng)?; Ok(tx) } diff --git a/src/api/recipient.rs b/src/api/recipient.rs index 88def03..b7a4899 100644 --- a/src/api/recipient.rs +++ b/src/api/recipient.rs @@ -68,6 +68,7 @@ pub fn decode_memo( recipient: &str, timestamp: u32, height: u32, + incoming: bool, ) -> ZMessage { let memo_lines: Vec<_> = memo.splitn(4, '\n').collect(); let msg = if memo_lines[0] == "\u{1F6E1}MSG" { @@ -83,6 +84,7 @@ pub fn decode_memo( body: memo_lines[3].to_string(), timestamp, height, + incoming, } } else { ZMessage { @@ -93,6 +95,7 @@ pub fn decode_memo( body: memo.to_string(), timestamp, height, + incoming, } }; msg diff --git a/src/api/sync.rs b/src/api/sync.rs index 78504c8..7fd5a9c 100644 --- a/src/api/sync.rs +++ b/src/api/sync.rs @@ -3,7 +3,7 @@ use crate::coinconfig::CoinConfig; use crate::scan::{AMProgressCallback, Progress}; use crate::sync::CTree; -use crate::{AccountData, BlockId, CompactTxStreamerClient, DbAdapter}; +use crate::{AccountData, BlockId, ChainError, CompactTxStreamerClient, DbAdapter}; use std::sync::Arc; use tokio::sync::Mutex; use tonic::transport::Channel; @@ -29,33 +29,14 @@ pub async fn coin_sync( cancel: &'static std::sync::Mutex, ) -> anyhow::Result<()> { let p_cb = Arc::new(Mutex::new(progress_callback)); - coin_sync_impl( - coin, - get_tx, - DEFAULT_CHUNK_SIZE, - anchor_offset, - max_cost, - p_cb.clone(), - cancel, - ) - .await?; - coin_sync_impl( - coin, - get_tx, - DEFAULT_CHUNK_SIZE, - 0, - u32::MAX, - p_cb.clone(), - cancel, - ) - .await?; + coin_sync_impl(coin, get_tx, anchor_offset, max_cost, p_cb.clone(), cancel).await?; + coin_sync_impl(coin, get_tx, 0, u32::MAX, p_cb.clone(), cancel).await?; Ok(()) } async fn coin_sync_impl( coin: u8, get_tx: bool, - chunk_size: u32, target_height_offset: u32, max_cost: u32, progress_callback: AMProgressCallback, @@ -63,15 +44,13 @@ async fn coin_sync_impl( ) -> anyhow::Result<()> { crate::scan::sync_async( coin, - chunk_size, get_tx, target_height_offset, max_cost, progress_callback, cancel, ) - .await?; - Ok(()) + .await } /// Return the latest block height @@ -135,9 +114,21 @@ async fn fetch_and_store_tree_state( .get_tree_state(Request::new(block_id)) .await? .into_inner(); - let tree = CTree::read(&*hex::decode(&tree_state.sapling_tree)?)?; // retrieve orchard state and store it too + let sapling_tree = CTree::read(&*hex::decode(&tree_state.sapling_tree)?)?; // retrieve orchard state and store it too + let orchard_tree = if !tree_state.orchard_tree.is_empty() { + CTree::read(&*hex::decode(&tree_state.orchard_tree)?)? // retrieve orchard state and store it too + } else { + CTree::new() + }; let db = c.db()?; - DbAdapter::store_block(&db.connection, height, &block.hash, block.time, &tree, None)?; + DbAdapter::store_block( + &db.connection, + height, + &block.hash, + block.time, + &sapling_tree, + &orchard_tree, + )?; Ok(()) } diff --git a/src/coinconfig.rs b/src/coinconfig.rs index 77d6fda..d364534 100644 --- a/src/coinconfig.rs +++ b/src/coinconfig.rs @@ -7,7 +7,7 @@ use lazycell::AtomicLazyCell; use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::{Arc, Mutex, MutexGuard}; use tonic::transport::Channel; -use zcash_params::coin::{get_coin_chain, CoinChain, CoinType}; +use zcash_params::coin::{get_coin_chain, get_coin_type, CoinChain, CoinType}; use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS}; use zcash_proofs::prover::LocalTxProver; @@ -56,6 +56,15 @@ pub fn get_coin_lwd_url(coin: u8) -> String { pub fn init_coin(coin: u8, db_path: &str) -> anyhow::Result<()> { let mut c = COIN_CONFIG[coin as usize].lock().unwrap(); c.set_db_path(db_path)?; + c.migrate_db()?; // if the db was already migrated, this is a no-op + c.open_db()?; + Ok(()) +} + +/// Upgrade database schema for given coin and db path +pub fn migrate_coin(coin: u8, db_path: &str) -> anyhow::Result<()> { + let chain = get_coin_chain(get_coin_type(coin)); + DbAdapter::migrate_db(chain.network(), db_path, chain.has_unified())?; Ok(()) } @@ -90,7 +99,21 @@ impl CoinConfig { pub fn set_db_path(&mut self, db_path: &str) -> anyhow::Result<()> { self.db_path = Some(db_path.to_string()); - let mut db = DbAdapter::new(self.coin_type, db_path)?; + Ok(()) + } + + pub fn migrate_db(&self) -> anyhow::Result<()> { + let network = self.chain.network(); + DbAdapter::migrate_db( + network, + self.db_path.as_ref().unwrap(), + self.chain.has_unified(), + )?; + Ok(()) + } + + pub fn open_db(&mut self) -> anyhow::Result<()> { + let mut db = DbAdapter::new(self.coin_type, self.db_path.as_ref().unwrap())?; db.init_db()?; self.db = Some(Arc::new(Mutex::new(db))); Ok(()) diff --git a/src/db.rs b/src/db.rs index af9e6a9..b7a7184 100644 --- a/src/db.rs +++ b/src/db.rs @@ -4,11 +4,11 @@ use crate::note_selection::{Source, UTXO}; use crate::orchard::{derive_orchard_keys, OrchardKeyBytes, OrchardViewKey}; use crate::prices::Quote; use crate::sapling::SaplingViewKey; +use crate::sync; use crate::sync::tree::{CTree, TreeCheckpoint}; use crate::taddr::{derive_tkeys, TBalance}; use crate::transaction::{GetTransactionDetailRequest, TransactionDetails}; use crate::unified::UnifiedAddressType; -use crate::{get_unified_address, sync}; use orchard::keys::FullViewingKey; use rusqlite::Error::QueryReturnedNoRows; use rusqlite::{params, Connection, OptionalExtension, Transaction}; @@ -113,6 +113,12 @@ impl DbAdapter { }) } + pub fn migrate_db(network: &Network, db_path: &str, has_ua: bool) -> anyhow::Result<()> { + let connection = Connection::open(db_path)?; + migration::init_db(&connection, network, has_ua)?; + Ok(()) + } + pub fn disable_wal(db_path: &str) -> anyhow::Result<()> { let connection = Connection::open(db_path)?; connection.query_row("PRAGMA journal_mode = OFF", [], |_| Ok(()))?; @@ -125,7 +131,6 @@ impl DbAdapter { } pub fn init_db(&mut self) -> anyhow::Result<()> { - migration::init_db(&self.connection, self.network())?; self.delete_incomplete_scan()?; Ok(()) } @@ -135,6 +140,21 @@ impl DbAdapter { Ok(()) } + pub fn get_account_id(&self, ivk: &str) -> anyhow::Result> { + let r = self + .connection + .query_row( + "SELECT id_account FROM accounts WHERE ivk = ?1", + params![ivk], + |r| { + let id: u32 = r.get(0)?; + Ok(id) + }, + ) + .optional()?; + Ok(r) + } + pub fn store_account( &self, name: &str, @@ -143,11 +163,7 @@ impl DbAdapter { sk: Option<&str>, ivk: &str, address: &str, - ) -> anyhow::Result<(u32, bool)> { - let mut statement = self - .connection - .prepare("SELECT id_account FROM accounts WHERE ivk = ?1")?; - let exists = statement.exists(params![ivk])?; + ) -> anyhow::Result { self.connection.execute( "INSERT INTO accounts(name, seed, aindex, sk, ivk, address) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", params![name, seed, index, sk, ivk, address], @@ -160,7 +176,7 @@ impl DbAdapter { |row| row.get(0), ) .map_err(wrap_query_no_rows("store_account/id_account"))?; - Ok((id_account, exists)) + Ok(id_account) } pub fn next_account_id(&self, seed: &str) -> anyhow::Result { @@ -231,6 +247,15 @@ impl DbAdapter { Ok(fvks) } + pub fn drop_last_checkpoint(&mut self) -> anyhow::Result { + let height = self.get_last_sync_height()?; + if let Some(height) = height { + let height = self.trim_to_height(height - 1)?; + return Ok(height); + } + Ok(self.sapling_activation_height()) + } + pub fn trim_to_height(&mut self, height: u32) -> anyhow::Result { // snap height to an existing checkpoint let height = self.connection.query_row( @@ -286,20 +311,25 @@ impl DbAdapter { hash: &[u8], timestamp: u32, sapling_tree: &CTree, - orchard_tree: Option<&CTree>, + orchard_tree: &CTree, ) -> anyhow::Result<()> { - log::debug!("+block"); + log::info!("+store_block"); let mut sapling_bb: Vec = vec![]; sapling_tree.write(&mut sapling_bb)?; - let orchard_bb = orchard_tree.map(|tree| { - let mut bb: Vec = vec![]; - tree.write(&mut bb).unwrap(); - bb - }); connection.execute( - "INSERT INTO blocks(height, hash, timestamp, sapling_tree, orchard_tree) - VALUES (?1, ?2, ?3, ?4, ?5)", - params![height, hash, timestamp, &sapling_bb, orchard_bb], + "INSERT INTO blocks(height, hash, timestamp) + VALUES (?1, ?2, ?3)", + params![height, hash, timestamp], + )?; + connection.execute( + "INSERT INTO sapling_tree(height, tree) VALUES (?1, ?2)", + params![height, &sapling_bb], + )?; + let mut orchard_bb: Vec = vec![]; + orchard_tree.write(&mut orchard_bb)?; + connection.execute( + "INSERT INTO orchard_tree(height, tree) VALUES (?1, ?2)", + params![height, &orchard_bb], )?; log::debug!("-block"); Ok(()) @@ -431,20 +461,25 @@ impl DbAdapter { } pub fn get_last_sync_height(&self) -> anyhow::Result> { - let height: Option = self - .connection - .query_row("SELECT MAX(height) FROM blocks", [], |row| row.get(0)) - .map_err(wrap_query_no_rows(""))?; + let height: Option = + self.connection + .query_row("SELECT MAX(height) FROM blocks", [], |row| row.get(0))?; + Ok(height) + } + + pub fn get_checkpoint_height(&self, max_height: u32) -> anyhow::Result> { + let height: Option = self.connection.query_row( + "SELECT MAX(height) FROM blocks WHERE height <= ?1", + [max_height], + |row| row.get(0), + )?; Ok(height) } pub fn get_db_height(&self) -> anyhow::Result { - let height: u32 = self.get_last_sync_height()?.unwrap_or_else(|| { - self.network() - .activation_height(NetworkUpgrade::Sapling) - .unwrap() - .into() - }); + let height: u32 = self + .get_last_sync_height()? + .unwrap_or_else(|| self.sapling_activation_height()); Ok(height) } @@ -556,15 +591,15 @@ impl DbAdapter { pub fn get_unspent_received_notes( &self, account: u32, - anchor_height: u32, + checkpoint_height: u32, ) -> anyhow::Result> { let mut notes = vec![]; + let mut statement = self.connection.prepare( "SELECT id_note, diversifier, value, rcm, witness FROM received_notes r, sapling_witnesses w WHERE spent IS NULL AND account = ?2 AND rho IS NULL - AND (r.excluded IS NULL OR NOT r.excluded) AND w.height = ( - SELECT MAX(height) FROM sapling_witnesses WHERE height <= ?1 - ) AND r.id_note = w.note")?; - let rows = statement.query_map(params![anchor_height, account], |row| { + AND (r.excluded IS NULL OR NOT r.excluded) AND w.height = ?1 + AND r.id_note = w.note")?; + let rows = statement.query_map(params![checkpoint_height, account], |row| { let id_note: u32 = row.get(0)?; let diversifier: Vec = row.get(1)?; let amount: i64 = row.get(2)?; @@ -589,10 +624,9 @@ impl DbAdapter { let mut statement = self.connection.prepare( "SELECT id_note, diversifier, value, rcm, rho, witness FROM received_notes r, orchard_witnesses w WHERE spent IS NULL AND account = ?2 AND rho IS NOT NULL - AND (r.excluded IS NULL OR NOT r.excluded) AND w.height = ( - SELECT MAX(height) FROM orchard_witnesses WHERE height <= ?1 - ) AND r.id_note = w.note")?; - let rows = statement.query_map(params![anchor_height, account], |row| { + AND (r.excluded IS NULL OR NOT r.excluded) AND w.height = ?1 + AND r.id_note = w.note")?; + let rows = statement.query_map(params![checkpoint_height, account], |row| { let id_note: u32 = row.get(0)?; let diversifier: Vec = row.get(1)?; let amount: i64 = row.get(2)?; @@ -968,6 +1002,10 @@ impl DbAdapter { "DELETE FROM orchard_addrs WHERE account = ?1", params![account], )?; + self.connection.execute( + "DELETE FROM ua_settings WHERE account = ?1", + params![account], + )?; self.connection .execute("DELETE FROM messages WHERE account = ?1", params![account])?; Ok(()) @@ -1031,8 +1069,8 @@ impl DbAdapter { } pub fn store_message(&self, account: u32, message: &ZMessage) -> anyhow::Result<()> { - self.connection.execute("INSERT INTO messages(account, id_tx, sender, recipient, subject, body, timestamp, height, read) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9)", - params![account, message.id_tx, message.sender, message.recipient, message.subject, message.body, message.timestamp, message.height, false])?; + self.connection.execute("INSERT INTO messages(account, id_tx, sender, recipient, subject, body, timestamp, height, incoming, read) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10)", + params![account, message.id_tx, message.sender, message.recipient, message.subject, message.body, message.timestamp, message.height, message.incoming, false])?; Ok(()) } @@ -1119,18 +1157,20 @@ impl DbAdapter { } pub fn get_txid_without_memo(&self) -> anyhow::Result> { - let mut stmt = self - .connection - .prepare("SELECT account, id_tx, height, txid FROM transactions WHERE memo IS NULL")?; + let mut stmt = self.connection.prepare( + "SELECT account, id_tx, height, timestamp, txid FROM transactions WHERE memo IS NULL", + )?; let rows = stmt.query_map([], |row| { let account: u32 = row.get(0)?; let id_tx: u32 = row.get(1)?; let height: u32 = row.get(2)?; - let txid: Vec = row.get(3)?; + let timestamp: u32 = row.get(3)?; + let txid: Vec = row.get(4)?; Ok(GetTransactionDetailRequest { account, id_tx, height, + timestamp, txid: txid.try_into().unwrap(), }) })?; @@ -1204,6 +1244,13 @@ impl DbAdapter { let chain = get_coin_chain(self.coin_type); chain.network() } + + pub fn sapling_activation_height(&self) -> u32 { + self.network() + .activation_height(NetworkUpgrade::Sapling) + .unwrap() + .into() + } } pub struct ZMessage { @@ -1214,6 +1261,7 @@ pub struct ZMessage { pub body: String, pub timestamp: u32, pub height: u32, + pub incoming: bool, } impl ZMessage { diff --git a/src/db/migration.rs b/src/db/migration.rs index 0399708..82a5fe8 100644 --- a/src/db/migration.rs +++ b/src/db/migration.rs @@ -34,7 +34,7 @@ pub fn reset_db(connection: &Connection) -> anyhow::Result<()> { Ok(()) } -pub fn init_db(connection: &Connection, network: &Network) -> anyhow::Result<()> { +pub fn init_db(connection: &Connection, network: &Network, has_ua: bool) -> anyhow::Result<()> { connection.execute( "CREATE TABLE IF NOT EXISTS schema_version ( id INTEGER PRIMARY KEY NOT NULL, @@ -195,7 +195,9 @@ pub fn init_db(connection: &Connection, network: &Network) -> anyhow::Result<()> orchard BOOL NOT NULL)", [], )?; - upgrade_accounts(&connection, network)?; + if has_ua { + upgrade_accounts(&connection, network)?; + } connection.execute( "CREATE TABLE sapling_tree( height INTEGER PRIMARY KEY, @@ -257,10 +259,15 @@ pub fn init_db(connection: &Connection, network: &Network) -> anyhow::Result<()> "CREATE INDEX IF NOT EXISTS i_orchard_witness ON orchard_witnesses(height)", [], )?; + connection.execute( + "ALTER TABLE messages ADD incoming BOOL NOT NULL DEFAULT (true)", + [], + )?; } if version != 5 { update_schema_version(connection, 5)?; + connection.cache_flush()?; log::info!("Database migrated"); } diff --git a/src/main/rpc.rs b/src/main/rpc.rs index fe4a58b..133d5d8 100644 --- a/src/main/rpc.rs +++ b/src/main/rpc.rs @@ -289,15 +289,17 @@ pub async fn pay(payment: Json, config: &State) -> Result) -> Result anyhow::Result> { let secp = Secp256k1::::new(); @@ -100,8 +99,9 @@ pub fn build_tx( }; let mut has_orchard = false; - let mut builder = Builder::new(*network, BlockHeight::from_u32(context.height)); - let anchor: Anchor = orchard::tree::MerkleHashOrchard::from_bytes(&context.orchard_anchor) + let mut builder = Builder::new(*network, BlockHeight::from_u32(plan.height)); + log::info!("ANCHOR {}", hex::encode(&plan.orchard_anchor)); + let anchor: Anchor = orchard::tree::MerkleHashOrchard::from_bytes(&plan.orchard_anchor) .unwrap() .into(); let mut orchard_builder = OrchardBuilder::new(Flags::from_parts(true, true), anchor); @@ -204,7 +204,7 @@ pub fn build_tx( get_prover(), &mut ctx, &mut rng, - BlockHeight::from_u32(context.height), + BlockHeight::from_u32(plan.height), None, ) .unwrap(); @@ -219,7 +219,7 @@ pub fn build_tx( TxVersion::Zip225, BranchId::Nu5, 0, - BlockHeight::from_u32(context.height + EXPIRY_HEIGHT), + BlockHeight::from_u32(plan.height + EXPIRY_HEIGHT), transparent_bundle, None, sapling_bundle, @@ -261,7 +261,7 @@ pub fn build_tx( TxVersion::Zip225, BranchId::Nu5, 0, - BlockHeight::from_u32(context.height + EXPIRY_HEIGHT), + BlockHeight::from_u32(plan.height + EXPIRY_HEIGHT), transparent_bundle, None, sapling_bundle, @@ -347,7 +347,9 @@ async fn dummy_test() { orders.push(Order::new(7, "uregtest1mxy5wq2n0xw57nuxa4lqpl358zw4vzyfgadsn5jungttmqcv6nx6cpx465dtpzjzw0vprjle4j4nqqzxtkuzm93regvgg4xce0un5ec6tedquc469zjhtdpkxz04kunqqyasv4rwvcweh3ue0ku0payn29stl2pwcrghyzscrrju9ar57rn36wgz74nmynwcyw27rjd8yk477l97ez8", 70000, MemoBytes::empty())); log::info!("Building tx plan"); - let tx_plan = build_tx_plan::("", 0, &utxos, &mut orders, &config).unwrap(); + let tx_plan = + build_tx_plan::("", 0, &[Hash::default(); 2], &utxos, &mut orders, &config) + .unwrap(); log::info!("Plan: {}", serde_json::to_string(&tx_plan).unwrap()); log::info!("Building tx"); diff --git a/src/note_selection/optimize.rs b/src/note_selection/optimize.rs index 2f97dae..faba10c 100644 --- a/src/note_selection/optimize.rs +++ b/src/note_selection/optimize.rs @@ -3,6 +3,7 @@ use crate::note_selection::fee::FeeCalculator; use crate::note_selection::ua::decode; use crate::note_selection::TransactionBuilderError::TxTooComplex; use crate::note_selection::{TransactionBuilderError, MAX_ATTEMPTS}; +use crate::Hash; use anyhow::anyhow; use std::str::FromStr; use zcash_primitives::memo::{Memo, MemoBytes}; @@ -282,6 +283,7 @@ pub fn outputs_for_change( pub fn build_tx_plan( fvk: &str, height: u32, + orchard_anchor: &Hash, utxos: &[UTXO], orders: &[Order], config: &TransactionBuilderConfig, @@ -312,6 +314,7 @@ pub fn build_tx_plan( let tx_plan = TransactionPlan { fvk: fvk.to_string(), height, + orchard_anchor: orchard_anchor.clone(), spends: notes, outputs: fills, net_chg, diff --git a/src/note_selection/ser.rs b/src/note_selection/ser.rs index 9939123..a6b32ff 100644 --- a/src/note_selection/ser.rs +++ b/src/note_selection/ser.rs @@ -57,7 +57,7 @@ impl TransactionReport { let net_sapling = outs[1] as i64 - spends[1] as i64; let net_orchard = outs[2] as i64 - spends[2] as i64; - let privacy_level = if outs[0] != 0 && spends[0] != 0 { + let privacy_level = if outs[0] - changes[0] != 0 && spends[0] != 0 { 0 // very low privacy: t2t } else if outs[0] != 0 || spends[0] != 0 { 1 // low privacy: t2z or z2t diff --git a/src/note_selection/tests.rs b/src/note_selection/tests.rs index 813ccac..584e175 100644 --- a/src/note_selection/tests.rs +++ b/src/note_selection/tests.rs @@ -5,6 +5,7 @@ use crate::note_selection::build_tx_plan; use crate::note_selection::fee::{FeeCalculator, FeeZIP327}; use crate::note_selection::optimize::{outputs_for_change, select_inputs}; use crate::note_selection::ua::decode; +use crate::Hash; use assert_matches::assert_matches; use serde::Serialize; use serde_json::Value; @@ -13,6 +14,7 @@ use zcash_primitives::memo::MemoBytes; macro_rules! utxo { ($id:expr, $q:expr) => { UTXO { + id: $id, amount: $q * 1000, source: Source::Transparent { txid: [0u8; 32], @@ -25,6 +27,7 @@ macro_rules! utxo { macro_rules! sapling { ($id:expr, $q:expr) => { UTXO { + id: $id, amount: $q * 1000, source: Source::Sapling { id_note: $id, @@ -39,6 +42,7 @@ macro_rules! sapling { macro_rules! orchard { ($id:expr, $q:expr) => { UTXO { + id: $id, amount: $q * 1000, source: Source::Orchard { id_note: $id, @@ -731,6 +735,7 @@ fn test_tx_plan() { let tx_plan = build_tx_plan::( "", 0, + &[Hash::default(); 2], &utxos, &orders, &TransactionBuilderConfig { diff --git a/src/note_selection/types.rs b/src/note_selection/types.rs index fcd7610..cc4d522 100644 --- a/src/note_selection/types.rs +++ b/src/note_selection/types.rs @@ -1,6 +1,7 @@ use super::ser::MemoBytesProxy; use crate::note_selection::ua::decode; use crate::unified::orchard_as_unified; +use crate::Hash; use orchard::Address; use serde::{Deserialize, Serialize}; use serde_hex::{SerHex, Strict}; @@ -124,9 +125,12 @@ pub struct RecipientShort { } #[derive(Serialize, Deserialize, Default)] +#[serde_as] pub struct TransactionPlan { pub fvk: String, pub height: u32, + #[serde(with = "SerHex::")] + pub orchard_anchor: Hash, pub spends: Vec, pub outputs: Vec, pub fee: u64, diff --git a/src/note_selection/utxo.rs b/src/note_selection/utxo.rs index 47ea4ed..ba161c6 100644 --- a/src/note_selection/utxo.rs +++ b/src/note_selection/utxo.rs @@ -5,9 +5,8 @@ use crate::CoinConfig; pub async fn fetch_utxos( coin: u8, account: u32, - last_height: u32, + checkpoint_height: u32, use_transparent_inputs: bool, - anchor_offset: u32, ) -> anyhow::Result> { let mut utxos = vec![]; if use_transparent_inputs { @@ -16,8 +15,7 @@ pub async fn fetch_utxos( let coin = CoinConfig::get(coin); let db = coin.db.as_ref().unwrap(); let db = db.lock().unwrap(); - let anchor_height = last_height.saturating_sub(anchor_offset); - utxos.extend(db.get_unspent_received_notes(account, anchor_height)?); + utxos.extend(db.get_unspent_received_notes(account, checkpoint_height)?); Ok(utxos) } diff --git a/src/orchard/hash.rs b/src/orchard/hash.rs index 0a2fef3..9f1b8f8 100644 --- a/src/orchard/hash.rs +++ b/src/orchard/hash.rs @@ -16,6 +16,7 @@ pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH"; lazy_static! { pub static ref ORCHARD_ROOTS: Vec = { + log::info!("Initialize Orchard Hasher"); let h = OrchardHasher::new(); h.empty_roots(32) }; @@ -37,7 +38,7 @@ impl OrchardHasher { let mut acc = self.Q; let (S_x, S_y) = SINSEMILLA_S[depth as usize]; let S_chunk = Affine::from_xy(S_x, S_y).unwrap(); - acc = (acc + S_chunk) + acc; // TODO Bail if + gives point at infinity? + acc = (acc + S_chunk) + acc; // TODO Bail if + gives point at infinity? Shouldn't happen if data was validated // Shift right by 1 bit and overwrite the 256th bit of left let mut left = *left; diff --git a/src/orchard/note.rs b/src/orchard/note.rs index 42235a8..60447ae 100644 --- a/src/orchard/note.rs +++ b/src/orchard/note.rs @@ -96,9 +96,13 @@ impl TrialDecrypter Vec { vtx.actions .iter() - .map(|co| { - let nf: [u8; 32] = co.nullifier.clone().try_into().unwrap(); - Nf(nf) + .filter_map(|co| { + if !co.nullifier.is_empty() { + let nf: [u8; 32] = co.nullifier.clone().try_into().unwrap(); + Some(Nf(nf)) + } else { + None + } }) .collect() } diff --git a/src/scan.rs b/src/scan.rs index fd6fe67..7f8e6e5 100644 --- a/src/scan.rs +++ b/src/scan.rs @@ -5,7 +5,7 @@ use serde::Serialize; use crate::chain::{download_chain, DecryptNode}; use crate::transaction::{get_transaction_details, retrieve_tx_info, GetTransactionDetailRequest}; use crate::{ - connect_lightwalletd, CoinConfig, CompactBlock, CompactSaplingOutput, CompactTx, + connect_lightwalletd, ChainError, CoinConfig, CompactBlock, CompactSaplingOutput, CompactTx, DbAdapterBuilder, }; @@ -78,8 +78,35 @@ type OrchardSynchronizer = Synchronizer< pub async fn sync_async<'a>( coin: u8, - _chunk_size: u32, - get_tx: bool, // TODO + get_tx: bool, + target_height_offset: u32, + max_cost: u32, + progress_callback: AMProgressCallback, // TODO + cancel: &'static std::sync::Mutex, +) -> anyhow::Result<()> { + let result = sync_async_inner( + coin, + get_tx, + target_height_offset, + max_cost, + progress_callback, + cancel, + ) + .await; + if let Err(ref e) = result { + if let Some(ChainError::Reorg) = e.downcast_ref::() { + log::info!("Drop latest checkpoint"); + let c = CoinConfig::get(coin); + let mut db = c.db()?; + db.drop_last_checkpoint()?; + } + } + result +} + +async fn sync_async_inner<'a>( + coin: u8, + get_tx: bool, target_height_offset: u32, max_cost: u32, progress_callback: AMProgressCallback, // TODO @@ -109,7 +136,7 @@ pub async fn sync_async<'a>( let mut height = start_height; let (blocks_tx, mut blocks_rx) = mpsc::channel::(1); - tokio::spawn(async move { + let downloader = tokio::spawn(async move { download_chain( &mut client, start_height, @@ -174,7 +201,9 @@ pub async fn sync_async<'a>( "orchard".to_string(), ); synchronizer.initialize(height)?; + log::info!("Process orchard start"); progress.trial_decryptions += synchronizer.process(&blocks.0)? as u64; + log::info!("Process orchard end"); } } @@ -185,6 +214,8 @@ pub async fn sync_async<'a>( cb(progress.clone()); } + downloader.await??; + if get_tx { get_transaction_details(coin).await?; } diff --git a/src/sync.rs b/src/sync.rs index 442fd34..9929fc2 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -85,24 +85,32 @@ impl< } pub fn process(&mut self, blocks: &[CompactBlock]) -> Result { + log::info!("="); if blocks.is_empty() { return Ok(0); } + log::info!("+1"); let decrypter = self.decrypter.clone(); + log::info!("+2"); let decrypted_blocks: Vec<_> = blocks .par_iter() .map(|b| decrypter.decrypt_notes(b, &self.vks)) .collect(); + log::info!("+"); let count_outputs: usize = decrypted_blocks .iter() .map(|b| b.count_outputs) .sum::() as usize; + log::info!("+"); let mut db = self.db.build()?; + log::info!("+"); self.warper.initialize(&self.tree, &self.witnesses); + log::info!("+"); let db_tx = db.begin_transaction()?; // Detect new received notes + log::info!("+"); let mut new_witnesses = vec![]; for decb in decrypted_blocks.iter() { for dectx in decb.txs.iter() { @@ -143,6 +151,7 @@ impl< } self.note_position += decb.count_outputs as usize; } + log::info!("+"); // Detect spends and collect note commitments let mut new_cmx = vec![]; @@ -172,18 +181,24 @@ impl< } // Run blocks through warp sync + log::info!("+"); self.warper.add_nodes(&mut new_cmx, &new_witnesses); + log::info!("+"); let (updated_tree, updated_witnesses) = self.warper.finalize(); // Store witnesses + log::info!("+"); for w in updated_witnesses.iter() { DbAdapter::store_witness(w, height, w.id_note, &db_tx, &self.shielded_pool)?; } + log::info!("+"); DbAdapter::store_tree(height, &updated_tree, &db_tx, &self.shielded_pool)?; + log::info!("+"); self.tree = updated_tree; self.witnesses = updated_witnesses; db_tx.commit()?; + log::info!("+"); Ok(count_outputs * self.vks.len()) } } diff --git a/src/sync/trial_decrypt.rs b/src/sync/trial_decrypt.rs index 848c51f..a0d542d 100644 --- a/src/sync/trial_decrypt.rs +++ b/src/sync/trial_decrypt.rs @@ -109,7 +109,11 @@ impl From<&CompactSaplingOutput> for CompactOutputBytes { impl From<&CompactOrchardAction> for CompactOutputBytes { fn from(co: &CompactOrchardAction) -> Self { CompactOutputBytes { - nullifier: co.nullifier.clone().try_into().unwrap(), + nullifier: if co.nullifier.is_empty() { + [0u8; 32] + } else { + co.nullifier.clone().try_into().unwrap() + }, epk: if co.ephemeral_key.is_empty() { [0u8; 32] } else { diff --git a/src/transaction.rs b/src/transaction.rs index fd722ec..8aca3c5 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,3 +1,4 @@ +use crate::api::recipient::decode_memo; use crate::contact::{Contact, ContactDecoder}; use crate::unified::orchard_as_unified; use crate::{AccountData, CoinConfig, CompactTxStreamerClient, DbAdapter, Hash, TxFilter}; @@ -41,8 +42,10 @@ pub async fn get_transaction_details(coin: u8) -> anyhow::Result<()> { let db = db.lock().unwrap(); let reqs = db.get_txid_without_memo()?; for req in reqs.iter() { - let decryption_keys = get_decryption_keys(network, req.account, &db)?; - keys.insert(req.account, decryption_keys); + if !keys.contains_key(&req.account) { + let decryption_keys = get_decryption_keys(network, req.account, &db)?; + keys.insert(req.account, decryption_keys); + } } reqs // Make sure we don't hold a mutex across await @@ -50,15 +53,7 @@ pub async fn get_transaction_details(coin: u8) -> anyhow::Result<()> { let mut details = vec![]; for req in reqs.iter() { - let tx_details = retrieve_tx_info( - network, - &mut client, - req.height, - req.id_tx, - &req.txid, - &keys[&req.account], - ) - .await?; + let tx_details = retrieve_tx_info(network, &mut client, req, &keys[&req.account]).await?; log::info!("{:?}", tx_details); details.push(tx_details); } @@ -70,6 +65,17 @@ pub async fn get_transaction_details(coin: u8) -> anyhow::Result<()> { for c in tx_details.contacts.iter() { db.store_contact(c, false)?; } + let z_msg = decode_memo( + tx_details.id_tx, + &tx_details.memo, + &tx_details.address, + tx_details.timestamp, + tx_details.height, + tx_details.incoming, + ); + if !z_msg.is_empty() { + db.store_message(tx_details.account, &z_msg)?; + } } Ok(()) @@ -103,14 +109,16 @@ pub struct DecryptionKeys { pub fn decode_transaction( network: &Network, + account: u32, height: u32, + timestamp: u32, id_tx: u32, tx: Transaction, decryption_keys: &DecryptionKeys, ) -> anyhow::Result { let (sapling_ivk, sapling_ovk) = decryption_keys.sapling_keys.clone(); - let height = BlockHeight::from_u32(height); + let block_height = BlockHeight::from_u32(height); let mut taddress: Option = None; let mut zaddress: Option = None; let mut oaddress: Option = None; @@ -120,6 +128,7 @@ pub fn decode_transaction( let mut tx_memo: Memo = Memo::Empty; let mut contacts = vec![]; + let mut incoming = true; if let Some(transparent_bundle) = tx.transparent_bundle() { for output in transparent_bundle.vout.iter() { @@ -138,7 +147,7 @@ pub fn decode_transaction( for output in sapling_bundle.shielded_outputs.iter() { let pivk = PreparedIncomingViewingKey::new(&sapling_ivk); if let Some((_note, pa, memo)) = - try_sapling_note_decryption(network, height, &pivk, output) + try_sapling_note_decryption(network, block_height, &pivk, output) { let memo = Memo::try_from(memo)?; if zaddress.is_none() { @@ -152,7 +161,7 @@ pub fn decode_transaction( } } if let Some((_note, pa, memo, ..)) = - try_sapling_output_recovery(network, height, &sapling_ovk, output) + try_sapling_output_recovery(network, block_height, &sapling_ovk, output) { let _ = contact_decoder.add_memo(&memo); // ignore memo that is not for contacts, if we cannot decode it with ovk, we didn't make create this memo zaddress = Some(encode_payment_address( @@ -162,6 +171,7 @@ pub fn decode_transaction( let memo = Memo::try_from(memo)?; if memo != Memo::Empty { tx_memo = memo; + incoming = false; } } } @@ -208,9 +218,13 @@ pub fn decode_transaction( Memo::Arbitrary(_) => "Unrecognized".to_string(), }; let tx_details = TransactionDetails { + account, id_tx, address, + height, + timestamp, memo, + incoming, contacts, }; @@ -243,13 +257,19 @@ fn get_decryption_keys( pub async fn retrieve_tx_info( network: &Network, client: &mut CompactTxStreamerClient, - height: u32, - id_tx: u32, - txid: &Hash, + req: &GetTransactionDetailRequest, decryption_keys: &DecryptionKeys, ) -> anyhow::Result { - let transaction = fetch_raw_transaction(network, client, height, txid).await?; - let tx_details = decode_transaction(network, height, id_tx, transaction, &decryption_keys)?; + let transaction = fetch_raw_transaction(network, client, req.height, &req.txid).await?; + let tx_details = decode_transaction( + network, + req.account, + req.height, + req.timestamp, + req.id_tx, + transaction, + &decryption_keys, + )?; Ok(tx_details) } @@ -257,15 +277,20 @@ pub async fn retrieve_tx_info( pub struct GetTransactionDetailRequest { pub account: u32, pub height: u32, + pub timestamp: u32, pub id_tx: u32, pub txid: Hash, } #[derive(Serialize, Debug)] pub struct TransactionDetails { + pub account: u32, pub id_tx: u32, + pub height: u32, + pub timestamp: u32, pub address: String, pub memo: String, + pub incoming: bool, pub contacts: Vec, } diff --git a/src/ua.rs b/src/ua.rs deleted file mode 100644 index e5721fe..0000000 --- a/src/ua.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! This file is not in use! - -use zcash_address::unified::{Address, Container, Receiver}; -use zcash_address::{FromAddress, Network, ToAddress, UnsupportedAddress, ZcashAddress}; - -#[derive(Debug, Clone)] -pub struct MyReceiver { - pub net: Network, - pub receiver: Receiver, -} - -impl FromAddress for MyReceiver { - fn from_sapling(net: Network, data: [u8; 43]) -> Result { - Ok(MyReceiver { - net, - receiver: Receiver::Sapling(data), - }) - } - - fn from_unified(net: Network, data: Address) -> Result { - for r in data.items_as_parsed().iter() { - match r { - Receiver::Sapling(data) => { - return Ok(MyReceiver { - net, - receiver: Receiver::Sapling(*data), - }); - } - _ => (), - } - } - FromAddress::from_unified(net, data) - } - - fn from_transparent_p2pkh(net: Network, data: [u8; 20]) -> Result { - Ok(MyReceiver { - net, - receiver: Receiver::P2pkh(data), - }) - } -} - -fn get_ua(_sapling_addr: &str, _transparent_addr: &str) -> anyhow::Result { - todo!() - // let sapling_addr = ZcashAddress::try_from_encoded(sapling_addr)?; - // let transparent_addr = ZcashAddress::try_from_encoded(transparent_addr)?; - // let receivers: Vec<_> = vec![sapling_addr, transparent_addr] - // .iter() - // .map(|r| r.clone().convert::().unwrap()) - // .collect(); - // let net = receivers.first().unwrap().net.clone(); - // let receivers: Vec<_> = receivers.iter().map(|r| r.receiver.clone()).collect(); - // let ua: Address = Address::from_inner(receivers)?; - // let ua_address = ZcashAddress::from_unified(net, ua); - // Ok(ua_address) -} - -fn get_sapling(ua_addr: &str) -> anyhow::Result { - let ua_addr = ZcashAddress::try_from_encoded(ua_addr)?; - let r = ua_addr.convert::()?; - if let Receiver::Sapling(data) = r.receiver { - return Ok(ZcashAddress::from_sapling(r.net, data)); - } - anyhow::bail!("Invalid UA"); -} - -#[cfg(test)] -mod tests { - use super::{get_sapling, get_ua}; - - #[test] - fn test_ua() -> anyhow::Result<()> { - let ua = get_ua( - "zs1lvzgfzzwl9n85446j292zg0valw2p47hmxnw42wnqsehsmyuvjk0mhxktcs0pqrplacm2vchh35", - "t1UWSSWaojmV5dgDhrSfZC6MAfCwVQ9LLoo", - )?; - let ua_str = ua.to_string(); - println!("{}", ua); - let za = get_sapling(&ua_str)?; - println!("{}", za); - - Ok(()) - } -} - -// t1UWSSWaojmV5dgDhrSfZC6MAfCwVQ9LLoo -// zs1lvzgfzzwl9n85446j292zg0valw2p47hmxnw42wnqsehsmyuvjk0mhxktcs0pqrplacm2vchh35 -// u16cdcqfguv574pnntjx7dfh78u8m5cu3myxyvs9gedkymstj60u366vpn9qhkcch77e26rzyecyhem7qnzrl7ws2huraj8se8tgek4t3ngn4lfs95l4774mhvgyea4jj93gm92jhg3z7