From b61772b639f682044f5b0a23402873943701c552 Mon Sep 17 00:00:00 2001 From: Hanh Date: Sun, 6 Nov 2022 09:49:17 +0800 Subject: [PATCH] Get Transaction Details --- src/api/account.rs | 4 +- src/api/sync.rs | 5 +- src/db.rs | 37 ++-- src/lib.rs | 5 + src/note_selection/tests.rs | 14 +- src/scan.rs | 32 +-- src/transaction.rs | 374 +++++++++++++----------------------- src/unified.rs | 13 +- 8 files changed, 190 insertions(+), 294 deletions(-) diff --git a/src/api/account.rs b/src/api/account.rs index a3bc843..7113392 100644 --- a/src/api/account.rs +++ b/src/api/account.rs @@ -290,9 +290,7 @@ pub async fn import_sync_data(coin: u8, file: &str) -> anyhow::Result<()> { let file = File::open(file)?; let file = BufReader::new(file); let account_info: AccountInfo = serde_json::from_reader(file)?; - let ids = db.import_from_syncdata(&account_info)?; - let mut client = connect_lightwalletd(c.lwd_url.as_ref().unwrap()).await?; - retrieve_tx_info(c.coin_type, &mut client, c.db_path.as_ref().unwrap(), &ids).await?; + db.import_from_syncdata(&account_info)?; Ok(()) } diff --git a/src/api/sync.rs b/src/api/sync.rs index ce8e405..7ccfef3 100644 --- a/src/api/sync.rs +++ b/src/api/sync.rs @@ -61,17 +61,14 @@ async fn coin_sync_impl( progress_callback: AMProgressCallback, cancel: &'static std::sync::Mutex, ) -> anyhow::Result<()> { - let c = CoinConfig::get(coin); crate::scan::sync_async( - c.coin_type, + coin, chunk_size, get_tx, - c.db_path.as_ref().unwrap(), target_height_offset, max_cost, progress_callback, cancel, - c.lwd_url.as_ref().unwrap(), ) .await?; Ok(()) diff --git a/src/db.rs b/src/db.rs index 445e4d1..6fbcd06 100644 --- a/src/db.rs +++ b/src/db.rs @@ -2,7 +2,7 @@ use crate::chain::{Nf, NfRef}; use crate::contact::Contact; use crate::prices::Quote; use crate::taddr::{derive_tkeys, TBalance}; -use crate::transaction::TransactionInfo; +use crate::transaction::{GetTransactionDetailRequest, TransactionDetails}; use crate::sync::tree::{CTree, TreeCheckpoint, Witness}; use rusqlite::Error::QueryReturnedNoRows; use rusqlite::{params, Connection, OptionalExtension, Transaction}; @@ -131,12 +131,7 @@ impl DbAdapter { let tx = self.connection.transaction()?; Ok(tx) } - // - // pub fn commit(&self) -> anyhow::Result<()> { - // self.connection.execute("COMMIT", [])?; - // Ok(()) - // } - // + pub fn init_db(&mut self) -> anyhow::Result<()> { migration::init_db(&self.connection, self.network())?; self.delete_incomplete_scan()?; @@ -478,11 +473,8 @@ impl DbAdapter { Ok(()) } - pub fn store_tx_metadata(&self, id_tx: u32, tx_info: &TransactionInfo) -> anyhow::Result<()> { - self.connection.execute( - "UPDATE transactions SET address = ?1, memo = ?2 WHERE id_tx = ?3", - params![tx_info.address, &tx_info.memo, id_tx], - )?; + pub fn update_transaction_with_memo(&self, id_tx: u32, details: &TransactionDetails) -> anyhow::Result<()> { + self.connection.execute("UPDATE transactions SET address = ?1, memo = ?2 WHERE id_tx = ?3", params![details.address, details.memo, id_tx])?; Ok(()) } @@ -1273,6 +1265,27 @@ impl DbAdapter { Ok(txs) } + 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 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)?; + Ok(GetTransactionDetailRequest { + account, + id_tx, + height, + txid: txid.try_into().unwrap(), + }) + })?; + let mut reqs = vec![]; + for r in rows { + reqs.push(r?); + } + Ok(reqs) + } + pub fn import_from_syncdata(&mut self, account_info: &AccountInfo) -> anyhow::Result> { // get id_account from fvk // truncate received_notes, sapling_witnesses for account diff --git a/src/lib.rs b/src/lib.rs index af8f8a3..7ac19fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,3 +145,8 @@ pub mod nodejs; mod gpu; +pub fn init_test() { + let _ = env_logger::try_init(); + init_coin(0, "./zec.db").unwrap(); + set_coin_lwd_url(0, "http://127.0.0.1:9067"); +} diff --git a/src/note_selection/tests.rs b/src/note_selection/tests.rs index 38332f5..d22f5dc 100644 --- a/src/note_selection/tests.rs +++ b/src/note_selection/tests.rs @@ -1,6 +1,6 @@ use serde_json::Value; use zcash_primitives::memo::Memo; -use crate::{CoinConfig, init_coin, set_coin_lwd_url}; +use crate::{CoinConfig, init_test}; use crate::api::payment::RecipientMemo; use crate::unified::UnifiedAddressType; use super::{*, types::*}; @@ -10,15 +10,9 @@ const CHANGE_ADDRESS: &str = "u1pncsxa8jt7aq37r8uvhjrgt7sv8a665hdw44rqa28cd9t6qq const UA_TSO: &str = "uregtest1mxy5wq2n0xw57nuxa4lqpl358zw4vzyfgadsn5jungttmqcv6nx6cpx465dtpzjzw0vprjle4j4nqqzxtkuzm93regvgg4xce0un5ec6tedquc469zjhtdpkxz04kunqqyasv4rwvcweh3ue0ku0payn29stl2pwcrghyzscrrju9ar57rn36wgz74nmynwcyw27rjd8yk477l97ez8"; const UA_O: &str = "uregtest1mzt5lx5s5u8kczlfr82av97kjckmfjfuq8y9849h6cl9chhdekxsm6r9dklracflqwplrnfzm5rucp5txfdm04z5myrde8y3y5rayev8"; -fn init() { - let _ = env_logger::try_init(); - init_coin(0, "./zec.db").unwrap(); - set_coin_lwd_url(0, "http://127.0.0.1:9067"); -} - #[tokio::test] async fn test_fetch_utxo() { - init(); + init_test(); let utxos = fetch_utxos(0, 1, 235, true, 0).await.unwrap(); for utxo in utxos.iter() { @@ -30,7 +24,7 @@ async fn test_fetch_utxo() { #[test] fn test_ua() { - init(); + init_test(); let c = CoinConfig::get(0); let db = c.db().unwrap(); let address = crate::get_unified_address(c.chain.network(), &db, 1, @@ -40,7 +34,7 @@ fn test_ua() { #[tokio::test] async fn test_payment() { - init(); + init_test(); let config = NoteSelectConfig::new(CHANGE_ADDRESS); let recipients = vec![ diff --git a/src/scan.rs b/src/scan.rs index efdc4db..6e32edb 100644 --- a/src/scan.rs +++ b/src/scan.rs @@ -1,9 +1,9 @@ use crate::chain::get_latest_height; -use crate::db::{AccountViewKey, DbAdapter}; +use crate::db::AccountViewKey; use serde::Serialize; -use crate::transaction::retrieve_tx_info; -use crate::{connect_lightwalletd, CompactBlock, CompactSaplingOutput, CompactTx, DbAdapterBuilder}; +use crate::transaction::{get_transaction_details, GetTransactionDetailRequest, retrieve_tx_info}; +use crate::{connect_lightwalletd, CompactBlock, CompactSaplingOutput, CompactTx, DbAdapterBuilder, CoinConfig}; use crate::chain::{DecryptNode, download_chain}; use anyhow::anyhow; @@ -15,7 +15,6 @@ use tokio::runtime::{Builder, Runtime}; use tokio::sync::mpsc; use tokio::sync::Mutex; use zcash_client_backend::encoding::decode_extended_full_viewing_key; -use zcash_params::coin::{get_coin_chain, CoinType}; use zcash_primitives::consensus::{Network, Parameters}; use zcash_primitives::sapling::Note; @@ -63,26 +62,24 @@ type OrchardSynchronizer = Synchronizer, OrchardHasher>; pub async fn sync_async<'a>( - coin_type: CoinType, + coin: u8, _chunk_size: u32, - _get_tx: bool, // TODO - db_path: &'a str, + get_tx: bool, // TODO target_height_offset: u32, max_cost: u32, _progress_callback: AMProgressCallback, // TODO cancel: &'static std::sync::Mutex, - ld_url: &'a str, ) -> anyhow::Result<()> { - let ld_url = ld_url.to_owned(); - let db_path = db_path.to_owned(); - let network = { - let chain = get_coin_chain(coin_type); - *chain.network() - }; + let c = CoinConfig::get(coin); + let ld_url = c.lwd_url.as_ref().unwrap().clone(); + let db_path = c.db_path.as_ref().unwrap().clone(); + + let network = *c.chain.network(); let mut client = connect_lightwalletd(&ld_url).await?; let (start_height, prev_hash, sapling_vks, orchard_vks) = { - let db = DbAdapter::new(coin_type, &db_path)?; + let db = c.db.as_ref().unwrap(); + let db = db.lock().unwrap(); let height = db.get_db_height()?; let hash = db.get_db_hash(height)?; let sapling_vks = db.get_sapling_fvks()?; @@ -102,7 +99,7 @@ pub async fn sync_async<'a>( Ok::<_, anyhow::Error>(()) }); - let db_builder = DbAdapterBuilder { coin_type, db_path: db_path.clone() }; + let db_builder = DbAdapterBuilder { coin_type: c.coin_type, db_path: db_path.clone() }; while let Some(blocks) = blocks_rx.recv().await { let first_block = blocks.0.first().unwrap(); // cannot be empty because blocks are not log::info!("Height: {}", first_block.height); @@ -148,6 +145,9 @@ pub async fn sync_async<'a>( height = last_height; } + if get_tx { + get_transaction_details(coin).await?; + } Ok(()) } diff --git a/src/transaction.rs b/src/transaction.rs index b3985a1..ee53b94 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,38 +1,23 @@ use crate::contact::{Contact, ContactDecoder}; -// use crate::wallet::decode_memo; -use crate::api::payment::decode_memo; -use crate::{CompactTxStreamerClient, DbAdapter, TxFilter}; -use anyhow::anyhow; -use futures::StreamExt; -use std::collections::HashMap; +use crate::{AccountData, CoinConfig, CompactTxStreamerClient, DbAdapter, Hash, TxFilter}; use std::convert::TryFrom; -use std::sync::mpsc; -use std::sync::mpsc::SyncSender; +use serde::Serialize; +use orchard::keys::{FullViewingKey, Scope}; +use orchard::note_encryption::OrchardDomain; +use orchard::value::ValueCommitment; use tonic::transport::Channel; use tonic::Request; +use zcash_address::{ToAddress, ZcashAddress}; use zcash_client_backend::encoding::{ decode_extended_full_viewing_key, encode_payment_address, encode_transparent_address, }; -use zcash_params::coin::{get_branch, get_coin_chain, CoinType}; +use zcash_note_encryption::{try_note_decryption, try_output_recovery_with_ovk}; +use zcash_params::coin::get_branch; use zcash_primitives::consensus::{BlockHeight, Network, Parameters}; -use zcash_primitives::memo::Memo; +use zcash_primitives::memo::{Memo, MemoBytes}; use zcash_primitives::sapling::note_encryption::{PreparedIncomingViewingKey, try_sapling_note_decryption, try_sapling_output_recovery}; use zcash_primitives::transaction::Transaction; -use zcash_primitives::zip32::ExtendedFullViewingKey; - -#[derive(Debug)] -pub struct TransactionInfo { - height: u32, - timestamp: u32, - index: u32, // index of tx in block - id_tx: u32, // id of tx in db - pub account: u32, - pub address: String, - pub memo: String, - pub amount: i64, - // pub fee: u64, - pub contacts: Vec, -} +use crate::unified::orchard_as_unified; #[derive(Debug)] pub struct ContactRef { @@ -41,96 +26,131 @@ pub struct ContactRef { pub contact: Contact, } -pub async fn decode_transaction( - network: &Network, - client: &mut CompactTxStreamerClient, - nfs: &HashMap<(u32, Vec), u64>, - id_tx: u32, - account: u32, - fvk: &ExtendedFullViewingKey, - tx_hash: &[u8], - height: u32, - timestamp: u32, - index: u32, -) -> anyhow::Result { - let consensus_branch_id = get_branch(network, height); - let ivk = fvk.fvk.vk.ivk(); - let ovk = fvk.fvk.ovk; +pub async fn get_transaction_details(coin: u8) -> anyhow::Result<()> { + let c = CoinConfig::get(coin); + let network = c.chain.network(); + let db = c.db.as_ref().unwrap(); + let db = db.lock().unwrap(); + let mut client = c.connect_lwd().await?; + let reqs = db.get_txid_without_memo()?; + for req in reqs { + let tx_details = retrieve_tx_info(network, &mut client, &db, req.account, req.height, &req.txid).await?; + log::info!("{:?}", tx_details); + db.update_transaction_with_memo(req.id_tx, &tx_details)?; + for c in tx_details.contacts.iter() { + db.store_contact(c, false)?; + } + } + Ok(()) +} +async fn fetch_raw_transaction(network: &Network, client: &mut CompactTxStreamerClient, height: u32, txid: &Hash) -> anyhow::Result { + let consensus_branch_id = get_branch(network, height); let tx_filter = TxFilter { block: None, index: 0, - hash: tx_hash.to_vec(), // only hash is supported + hash: txid.to_vec(), // only hash is supported }; let raw_tx = client .get_transaction(Request::new(tx_filter)) .await? .into_inner(); let tx = Transaction::read(&*raw_tx.data, consensus_branch_id)?; + Ok(tx) +} + +pub fn decode_transaction( + network: &Network, + account: u32, + height: u32, + tx: Transaction, + db: &DbAdapter, +) -> anyhow::Result { + let AccountData { fvk, .. } = db.get_account_info(account)?; + let fvk = decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &fvk).unwrap(); + let sapling_ivk = fvk.fvk.vk.ivk(); + let sapling_ovk = fvk.fvk.ovk; + + let okey = db.get_orchard(account)?; + let okey = okey.map(|okey| { + let fvk = FullViewingKey::from_bytes(&okey.fvk).unwrap(); + (fvk.to_ivk(Scope::External), fvk.to_ovk(Scope::External)) + }); let height = BlockHeight::from_u32(height); - let mut amount = 0i64; - let mut taddress = String::new(); - let mut zaddress = String::new(); + let mut taddress: Option = None; + let mut zaddress: Option = None; + let mut oaddress: Option = None; let tx = tx.into_data(); // log::info!("{:?}", tx); - let sapling_bundle = tx.sapling_bundle().ok_or(anyhow!("No sapling bundle"))?; - for spend in sapling_bundle.shielded_spends.iter() { - let nf = spend.nullifier.to_vec(); - if let Some(&v) = nfs.get(&(account, nf)) { - amount -= v as i64; - } - } - - let mut contact_decoder = ContactDecoder::new(sapling_bundle.shielded_outputs.len()); let mut tx_memo: Memo = Memo::Empty; + let mut contacts = vec![]; if let Some(transparent_bundle) = tx.transparent_bundle() { for output in transparent_bundle.vout.iter() { if let Some(taddr) = output.recipient_address() { - taddress = encode_transparent_address( + taddress = Some(encode_transparent_address( &network.b58_pubkey_address_prefix(), &network.b58_script_address_prefix(), &taddr, - ); + )); } } } - for output in sapling_bundle.shielded_outputs.iter() { - let pivk = PreparedIncomingViewingKey::new(&ivk); - if let Some((note, pa, memo)) = try_sapling_note_decryption(network, height, &pivk, output) { - amount += note.value as i64; // change or self transfer - let _ = contact_decoder.add_memo(&memo); // ignore memo that is not for contacts - let memo = Memo::try_from(memo)?; - if zaddress.is_empty() { - zaddress = encode_payment_address(network.hrp_sapling_payment_address(), &pa); + if let Some(sapling_bundle) = tx.sapling_bundle() { + let mut contact_decoder = ContactDecoder::new(sapling_bundle.shielded_outputs.len()); + 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) { + let memo = Memo::try_from(memo)?; + if zaddress.is_none() { + zaddress = Some(encode_payment_address(network.hrp_sapling_payment_address(), &pa)); + } + if memo != Memo::Empty { + tx_memo = memo; + } } - if memo != Memo::Empty { - tx_memo = memo; + if let Some((_note, pa, memo, ..)) = try_sapling_output_recovery(network, 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(network.hrp_sapling_payment_address(), &pa)); + let memo = Memo::try_from(memo)?; + if memo != Memo::Empty { + tx_memo = memo; + } } - } else if let Some((_note, pa, memo)) = - try_sapling_output_recovery(network, height, &ovk, output) - { - zaddress = encode_payment_address(network.hrp_sapling_payment_address(), &pa); - let memo = Memo::try_from(memo)?; - if memo != Memo::Empty { - tx_memo = memo; + } + contacts = contact_decoder.finalize()?; + } + + if let Some(orchard_bundle) = tx.orchard_bundle() { + if let Some((orchard_ivk, orchard_ovk)) = okey { + for action in orchard_bundle.actions().iter() { + let domain = OrchardDomain::for_action(action); + if let Some((_note, pa, memo)) = try_note_decryption(&domain, &orchard_ivk, action) { + let memo = Memo::try_from(MemoBytes::from_bytes(&memo)?)?; + if oaddress.is_none() { + oaddress = Some(orchard_as_unified(network, &pa).encode()); + } + if memo != Memo::Empty { + tx_memo = memo; + } + } + if let Some((_note, pa, memo, ..)) = try_output_recovery_with_ovk(&domain, &orchard_ovk, action, + action.cv_net(), &action.encrypted_note().out_ciphertext) { + let memo = Memo::try_from(MemoBytes::from_bytes(&memo)?)?; + oaddress = Some(orchard_as_unified(network, &pa).encode()); + if memo != Memo::Empty { + tx_memo = memo; + } + } } } } - // let fee = - // u64::from() + - // u64::from(tx.sapling_bundle().unwrap().value_balance); - - // zaddress must be one of ours - // taddress is not always ours - let address = - // let's use the zaddr from ovk first, then the ivk then the taddr - if zaddress.is_empty() { taddress } else { zaddress }; + let address = zaddress.or(oaddress).or(taddress).unwrap_or(String::new()); let memo = match tx_memo { Memo::Empty => "".to_string(), @@ -138,178 +158,46 @@ pub async fn decode_transaction( Memo::Future(_) => "Unrecognized".to_string(), Memo::Arbitrary(_) => "Unrecognized".to_string(), }; - let contacts = contact_decoder.finalize()?; - let tx_info = TransactionInfo { - height: u32::from(height), - timestamp, - index, - id_tx, - account, + let tx_details = TransactionDetails { address, memo, - amount, - // fee, contacts, }; - Ok(tx_info) -} - -struct DecodeTxParams<'a> { - tx: SyncSender, - client: CompactTxStreamerClient, - nf_map: &'a HashMap<(u32, Vec), u64>, - index: u32, - id_tx: u32, - account: u32, - fvk: ExtendedFullViewingKey, - tx_hash: Vec, - height: u32, - timestamp: u32, + Ok(tx_details) } pub async fn retrieve_tx_info( - coin_type: CoinType, + network: &Network, client: &mut CompactTxStreamerClient, - db_path: &str, - tx_ids: &[u32], -) -> anyhow::Result<()> { - let network = { - let chain = get_coin_chain(coin_type); - *chain.network() - }; - let db = DbAdapter::new(coin_type, db_path)?; + db: &DbAdapter, + account: u32, + height: u32, + txid: &Hash, +) -> anyhow::Result { + let transaction = fetch_raw_transaction(network, client, height, txid).await?; + let tx_details = decode_transaction(network, account, height, transaction, db)?; - let nfs = db.get_nullifiers_raw()?; - let mut nf_map: HashMap<(u32, Vec), u64> = HashMap::new(); - for nf in nfs.iter() { - nf_map.insert((nf.0, nf.2.clone()), nf.1); - } - let mut fvk_cache: HashMap = HashMap::new(); - let mut decode_tx_params: Vec = vec![]; - let (tx, rx) = mpsc::sync_channel::(4); - for (index, &id_tx) in tx_ids.iter().enumerate() { - let (account, height, timestamp, tx_hash, ivk) = db.get_txhash(id_tx)?; - let fvk: &ExtendedFullViewingKey = fvk_cache.entry(account).or_insert_with(|| { - decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &ivk) - .unwrap() - }); - let params = DecodeTxParams { - tx: tx.clone(), - client: client.clone(), - nf_map: &nf_map, - index: index as u32, - id_tx, - account, - fvk: fvk.clone(), - tx_hash: tx_hash.clone(), - height, - timestamp, - }; - decode_tx_params.push(params); - } - - let res = tokio_stream::iter(decode_tx_params).for_each_concurrent(None, |mut p| async move { - if let Ok(tx_info) = decode_transaction( - &network, - &mut p.client, - p.nf_map, - p.id_tx, - p.account, - &p.fvk, - &p.tx_hash, - p.height, - p.timestamp, - p.index, - ) - .await - { - p.tx.send(tx_info).unwrap(); - drop(p.tx); - } - }); - - let f = tokio::spawn(async move { - let mut contacts: Vec = vec![]; - while let Ok(tx_info) = rx.recv() { - for c in tx_info.contacts.iter() { - contacts.push(ContactRef { - height: tx_info.height, - index: tx_info.index, - contact: c.clone(), - }); - } - db.store_tx_metadata(tx_info.id_tx, &tx_info)?; - let z_msg = decode_memo( - tx_info.id_tx, - &tx_info.memo, - &tx_info.address, - tx_info.timestamp, - tx_info.height, - ); - if !z_msg.is_empty() { - db.store_message(tx_info.account, &z_msg)?; - } - } - contacts.sort_by(|a, b| a.index.cmp(&b.index)); - for cref in contacts.iter() { - db.store_contact(&cref.contact, false)?; - } - - Ok::<_, anyhow::Error>(()) - }); - - res.await; - drop(tx); - f.await??; - - Ok(()) + Ok(tx_details) } -#[cfg(test)] -mod tests { - use crate::transaction::decode_transaction; - use crate::{AccountData, connect_lightwalletd, DbAdapter, LWD_URL}; - use std::collections::HashMap; - use zcash_client_backend::encoding::decode_extended_full_viewing_key; - use zcash_params::coin::CoinType; - use zcash_primitives::consensus::{Network, Parameters}; - - #[tokio::test] - async fn test_decode_transaction() { - let tx_hash = - hex::decode("b47da170329dc311b98892eac23e83025f8bb3ce10bb07535698c91fb37e1e54") - .unwrap(); - let mut client = connect_lightwalletd(LWD_URL).await.unwrap(); - let db = DbAdapter::new(CoinType::Zcash, "./zec.db").unwrap(); - let account = 1; - let nfs = db.get_nullifiers_raw().unwrap(); - let mut nf_map: HashMap<(u32, Vec), u64> = HashMap::new(); - for nf in nfs.iter() { - if nf.0 == account { - nf_map.insert((nf.0, nf.2.clone()), nf.1); - } - } - let AccountData { fvk, .. } = db.get_account_info(account).unwrap(); - let fvk = decode_extended_full_viewing_key( - Network::MainNetwork.hrp_sapling_extended_full_viewing_key(), - &fvk, - ) - .unwrap(); - let tx_info = decode_transaction( - &Network::MainNetwork, - &mut client, - &nf_map, - 1, - account, - &fvk, - &tx_hash, - 1313212, - 1000, - 1, - ) - .await - .unwrap(); - println!("{:?}", tx_info); - } +pub struct GetTransactionDetailRequest { + pub account: u32, + pub height: u32, + pub id_tx: u32, + pub txid: Hash, +} + +#[derive(Serialize, Debug)] +pub struct TransactionDetails { + pub address: String, + pub memo: String, + pub contacts: Vec, +} + +#[tokio::test] +async fn test_get_transaction_details() { + crate::init_test(); + + get_transaction_details(0).await.unwrap(); } diff --git a/src/unified.rs b/src/unified.rs index 7935ff3..cd723c3 100644 --- a/src/unified.rs +++ b/src/unified.rs @@ -29,7 +29,7 @@ impl std::fmt::Display for DecodedUA { self.sapling.as_ref().map(|a| encode_payment_address(self.network.hrp_sapling_payment_address(), a)), self.orchard.as_ref().map(|a| { let ua = unified::Address(vec![Receiver::Orchard(a.to_raw_address_bytes())]); - ua.encode(&network2network(&self.network)) + ua.encode(&self.network.address_network().unwrap()) }) ) } @@ -66,7 +66,7 @@ pub fn get_unified_address(network: &Network, db: &DbAdapter, account: u32, tpe: } let addresses = unified::Address(rcvs); - let unified_address = ZcashAddress::from_unified(network2network(network), addresses); + let unified_address = ZcashAddress::from_unified(network.address_network().unwrap(), addresses); Ok(unified_address.encode()) } @@ -77,7 +77,7 @@ pub fn decode_unified_address(network: &Network, ua: &str) -> anyhow::Result anyhow::Result zcash_address::Network { n.address_network().unwrap() } - -// u1pncsxa8jt7aq37r8uvhjrgt7sv8a665hdw44rqa28cd9t6qqmktzwktw772nlle6skkkxwmtzxaan3slntqev03g70tzpky3c58hfgvfjkcky255cwqgfuzdjcktfl7pjalt5sl33se75pmga09etn9dplr98eq2g8cgmvgvx6jx2a2xhy39x96c6rumvlyt35whml87r064qdzw30e \ No newline at end of file +pub fn orchard_as_unified(network: &Network, address: &Address) -> ZcashAddress { + let unified_address = unified::Address(vec![Receiver::Orchard(address.to_raw_address_bytes())]); + ZcashAddress::from_unified(network.address_network().unwrap(), unified_address) +}