Get Transaction Details

This commit is contained in:
Hanh 2022-11-06 09:49:17 +08:00
parent f222187650
commit b61772b639
8 changed files with 190 additions and 294 deletions

View File

@ -290,9 +290,7 @@ pub async fn import_sync_data(coin: u8, file: &str) -> anyhow::Result<()> {
let file = File::open(file)?; let file = File::open(file)?;
let file = BufReader::new(file); let file = BufReader::new(file);
let account_info: AccountInfo = serde_json::from_reader(file)?; let account_info: AccountInfo = serde_json::from_reader(file)?;
let ids = db.import_from_syncdata(&account_info)?; 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?;
Ok(()) Ok(())
} }

View File

@ -61,17 +61,14 @@ async fn coin_sync_impl(
progress_callback: AMProgressCallback, progress_callback: AMProgressCallback,
cancel: &'static std::sync::Mutex<bool>, cancel: &'static std::sync::Mutex<bool>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
crate::scan::sync_async( crate::scan::sync_async(
c.coin_type, coin,
chunk_size, chunk_size,
get_tx, get_tx,
c.db_path.as_ref().unwrap(),
target_height_offset, target_height_offset,
max_cost, max_cost,
progress_callback, progress_callback,
cancel, cancel,
c.lwd_url.as_ref().unwrap(),
) )
.await?; .await?;
Ok(()) Ok(())

View File

@ -2,7 +2,7 @@ use crate::chain::{Nf, NfRef};
use crate::contact::Contact; use crate::contact::Contact;
use crate::prices::Quote; use crate::prices::Quote;
use crate::taddr::{derive_tkeys, TBalance}; use crate::taddr::{derive_tkeys, TBalance};
use crate::transaction::TransactionInfo; use crate::transaction::{GetTransactionDetailRequest, TransactionDetails};
use crate::sync::tree::{CTree, TreeCheckpoint, Witness}; use crate::sync::tree::{CTree, TreeCheckpoint, Witness};
use rusqlite::Error::QueryReturnedNoRows; use rusqlite::Error::QueryReturnedNoRows;
use rusqlite::{params, Connection, OptionalExtension, Transaction}; use rusqlite::{params, Connection, OptionalExtension, Transaction};
@ -131,12 +131,7 @@ impl DbAdapter {
let tx = self.connection.transaction()?; let tx = self.connection.transaction()?;
Ok(tx) Ok(tx)
} }
//
// pub fn commit(&self) -> anyhow::Result<()> {
// self.connection.execute("COMMIT", [])?;
// Ok(())
// }
//
pub fn init_db(&mut self) -> anyhow::Result<()> { pub fn init_db(&mut self) -> anyhow::Result<()> {
migration::init_db(&self.connection, self.network())?; migration::init_db(&self.connection, self.network())?;
self.delete_incomplete_scan()?; self.delete_incomplete_scan()?;
@ -478,11 +473,8 @@ impl DbAdapter {
Ok(()) Ok(())
} }
pub fn store_tx_metadata(&self, id_tx: u32, tx_info: &TransactionInfo) -> anyhow::Result<()> { pub fn update_transaction_with_memo(&self, id_tx: u32, details: &TransactionDetails) -> anyhow::Result<()> {
self.connection.execute( self.connection.execute("UPDATE transactions SET address = ?1, memo = ?2 WHERE id_tx = ?3", params![details.address, details.memo, id_tx])?;
"UPDATE transactions SET address = ?1, memo = ?2 WHERE id_tx = ?3",
params![tx_info.address, &tx_info.memo, id_tx],
)?;
Ok(()) Ok(())
} }
@ -1273,6 +1265,27 @@ impl DbAdapter {
Ok(txs) Ok(txs)
} }
pub fn get_txid_without_memo(&self) -> anyhow::Result<Vec<GetTransactionDetailRequest>> {
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<u8> = 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<Vec<u32>> { pub fn import_from_syncdata(&mut self, account_info: &AccountInfo) -> anyhow::Result<Vec<u32>> {
// get id_account from fvk // get id_account from fvk
// truncate received_notes, sapling_witnesses for account // truncate received_notes, sapling_witnesses for account

View File

@ -145,3 +145,8 @@ pub mod nodejs;
mod gpu; 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");
}

View File

@ -1,6 +1,6 @@
use serde_json::Value; use serde_json::Value;
use zcash_primitives::memo::Memo; 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::api::payment::RecipientMemo;
use crate::unified::UnifiedAddressType; use crate::unified::UnifiedAddressType;
use super::{*, types::*}; use super::{*, types::*};
@ -10,15 +10,9 @@ const CHANGE_ADDRESS: &str = "u1pncsxa8jt7aq37r8uvhjrgt7sv8a665hdw44rqa28cd9t6qq
const UA_TSO: &str = "uregtest1mxy5wq2n0xw57nuxa4lqpl358zw4vzyfgadsn5jungttmqcv6nx6cpx465dtpzjzw0vprjle4j4nqqzxtkuzm93regvgg4xce0un5ec6tedquc469zjhtdpkxz04kunqqyasv4rwvcweh3ue0ku0payn29stl2pwcrghyzscrrju9ar57rn36wgz74nmynwcyw27rjd8yk477l97ez8"; const UA_TSO: &str = "uregtest1mxy5wq2n0xw57nuxa4lqpl358zw4vzyfgadsn5jungttmqcv6nx6cpx465dtpzjzw0vprjle4j4nqqzxtkuzm93regvgg4xce0un5ec6tedquc469zjhtdpkxz04kunqqyasv4rwvcweh3ue0ku0payn29stl2pwcrghyzscrrju9ar57rn36wgz74nmynwcyw27rjd8yk477l97ez8";
const UA_O: &str = "uregtest1mzt5lx5s5u8kczlfr82av97kjckmfjfuq8y9849h6cl9chhdekxsm6r9dklracflqwplrnfzm5rucp5txfdm04z5myrde8y3y5rayev8"; 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] #[tokio::test]
async fn test_fetch_utxo() { async fn test_fetch_utxo() {
init(); init_test();
let utxos = fetch_utxos(0, 1, 235, true, 0).await.unwrap(); let utxos = fetch_utxos(0, 1, 235, true, 0).await.unwrap();
for utxo in utxos.iter() { for utxo in utxos.iter() {
@ -30,7 +24,7 @@ async fn test_fetch_utxo() {
#[test] #[test]
fn test_ua() { fn test_ua() {
init(); init_test();
let c = CoinConfig::get(0); let c = CoinConfig::get(0);
let db = c.db().unwrap(); let db = c.db().unwrap();
let address = crate::get_unified_address(c.chain.network(), &db, 1, let address = crate::get_unified_address(c.chain.network(), &db, 1,
@ -40,7 +34,7 @@ fn test_ua() {
#[tokio::test] #[tokio::test]
async fn test_payment() { async fn test_payment() {
init(); init_test();
let config = NoteSelectConfig::new(CHANGE_ADDRESS); let config = NoteSelectConfig::new(CHANGE_ADDRESS);
let recipients = vec![ let recipients = vec![

View File

@ -1,9 +1,9 @@
use crate::chain::get_latest_height; use crate::chain::get_latest_height;
use crate::db::{AccountViewKey, DbAdapter}; use crate::db::AccountViewKey;
use serde::Serialize; use serde::Serialize;
use crate::transaction::retrieve_tx_info; use crate::transaction::{get_transaction_details, GetTransactionDetailRequest, retrieve_tx_info};
use crate::{connect_lightwalletd, CompactBlock, CompactSaplingOutput, CompactTx, DbAdapterBuilder}; use crate::{connect_lightwalletd, CompactBlock, CompactSaplingOutput, CompactTx, DbAdapterBuilder, CoinConfig};
use crate::chain::{DecryptNode, download_chain}; use crate::chain::{DecryptNode, download_chain};
use anyhow::anyhow; use anyhow::anyhow;
@ -15,7 +15,6 @@ use tokio::runtime::{Builder, Runtime};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use zcash_client_backend::encoding::decode_extended_full_viewing_key; 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::consensus::{Network, Parameters};
use zcash_primitives::sapling::Note; use zcash_primitives::sapling::Note;
@ -63,26 +62,24 @@ type OrchardSynchronizer = Synchronizer<Network, OrchardDomain, OrchardViewKey,
OrchardDecrypter<Network>, OrchardHasher>; OrchardDecrypter<Network>, OrchardHasher>;
pub async fn sync_async<'a>( pub async fn sync_async<'a>(
coin_type: CoinType, coin: u8,
_chunk_size: u32, _chunk_size: u32,
_get_tx: bool, // TODO get_tx: bool, // TODO
db_path: &'a str,
target_height_offset: u32, target_height_offset: u32,
max_cost: u32, max_cost: u32,
_progress_callback: AMProgressCallback, // TODO _progress_callback: AMProgressCallback, // TODO
cancel: &'static std::sync::Mutex<bool>, cancel: &'static std::sync::Mutex<bool>,
ld_url: &'a str,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let ld_url = ld_url.to_owned(); let c = CoinConfig::get(coin);
let db_path = db_path.to_owned(); let ld_url = c.lwd_url.as_ref().unwrap().clone();
let network = { let db_path = c.db_path.as_ref().unwrap().clone();
let chain = get_coin_chain(coin_type);
*chain.network() let network = *c.chain.network();
};
let mut client = connect_lightwalletd(&ld_url).await?; let mut client = connect_lightwalletd(&ld_url).await?;
let (start_height, prev_hash, sapling_vks, orchard_vks) = { 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 height = db.get_db_height()?;
let hash = db.get_db_hash(height)?; let hash = db.get_db_hash(height)?;
let sapling_vks = db.get_sapling_fvks()?; let sapling_vks = db.get_sapling_fvks()?;
@ -102,7 +99,7 @@ pub async fn sync_async<'a>(
Ok::<_, anyhow::Error>(()) 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 { while let Some(blocks) = blocks_rx.recv().await {
let first_block = blocks.0.first().unwrap(); // cannot be empty because blocks are not let first_block = blocks.0.first().unwrap(); // cannot be empty because blocks are not
log::info!("Height: {}", first_block.height); log::info!("Height: {}", first_block.height);
@ -148,6 +145,9 @@ pub async fn sync_async<'a>(
height = last_height; height = last_height;
} }
if get_tx {
get_transaction_details(coin).await?;
}
Ok(()) Ok(())
} }

View File

@ -1,38 +1,23 @@
use crate::contact::{Contact, ContactDecoder}; use crate::contact::{Contact, ContactDecoder};
// use crate::wallet::decode_memo; use crate::{AccountData, CoinConfig, CompactTxStreamerClient, DbAdapter, Hash, TxFilter};
use crate::api::payment::decode_memo;
use crate::{CompactTxStreamerClient, DbAdapter, TxFilter};
use anyhow::anyhow;
use futures::StreamExt;
use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::sync::mpsc; use serde::Serialize;
use std::sync::mpsc::SyncSender; use orchard::keys::{FullViewingKey, Scope};
use orchard::note_encryption::OrchardDomain;
use orchard::value::ValueCommitment;
use tonic::transport::Channel; use tonic::transport::Channel;
use tonic::Request; use tonic::Request;
use zcash_address::{ToAddress, ZcashAddress};
use zcash_client_backend::encoding::{ use zcash_client_backend::encoding::{
decode_extended_full_viewing_key, encode_payment_address, encode_transparent_address, 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::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::sapling::note_encryption::{PreparedIncomingViewingKey, try_sapling_note_decryption, try_sapling_output_recovery};
use zcash_primitives::transaction::Transaction; use zcash_primitives::transaction::Transaction;
use zcash_primitives::zip32::ExtendedFullViewingKey; use crate::unified::orchard_as_unified;
#[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<Contact>,
}
#[derive(Debug)] #[derive(Debug)]
pub struct ContactRef { pub struct ContactRef {
@ -41,96 +26,131 @@ pub struct ContactRef {
pub contact: Contact, pub contact: Contact,
} }
pub async fn decode_transaction( pub async fn get_transaction_details(coin: u8) -> anyhow::Result<()> {
network: &Network, let c = CoinConfig::get(coin);
client: &mut CompactTxStreamerClient<Channel>, let network = c.chain.network();
nfs: &HashMap<(u32, Vec<u8>), u64>, let db = c.db.as_ref().unwrap();
id_tx: u32, let db = db.lock().unwrap();
account: u32, let mut client = c.connect_lwd().await?;
fvk: &ExtendedFullViewingKey, let reqs = db.get_txid_without_memo()?;
tx_hash: &[u8], for req in reqs {
height: u32, let tx_details = retrieve_tx_info(network, &mut client, &db, req.account, req.height, &req.txid).await?;
timestamp: u32, log::info!("{:?}", tx_details);
index: u32, db.update_transaction_with_memo(req.id_tx, &tx_details)?;
) -> anyhow::Result<TransactionInfo> { for c in tx_details.contacts.iter() {
let consensus_branch_id = get_branch(network, height); db.store_contact(c, false)?;
let ivk = fvk.fvk.vk.ivk(); }
let ovk = fvk.fvk.ovk; }
Ok(())
}
async fn fetch_raw_transaction(network: &Network, client: &mut CompactTxStreamerClient<Channel>, height: u32, txid: &Hash) -> anyhow::Result<Transaction> {
let consensus_branch_id = get_branch(network, height);
let tx_filter = TxFilter { let tx_filter = TxFilter {
block: None, block: None,
index: 0, index: 0,
hash: tx_hash.to_vec(), // only hash is supported hash: txid.to_vec(), // only hash is supported
}; };
let raw_tx = client let raw_tx = client
.get_transaction(Request::new(tx_filter)) .get_transaction(Request::new(tx_filter))
.await? .await?
.into_inner(); .into_inner();
let tx = Transaction::read(&*raw_tx.data, consensus_branch_id)?; 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<TransactionDetails> {
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 height = BlockHeight::from_u32(height);
let mut amount = 0i64; let mut taddress: Option<String> = None;
let mut taddress = String::new(); let mut zaddress: Option<String> = None;
let mut zaddress = String::new(); let mut oaddress: Option<String> = None;
let tx = tx.into_data(); let tx = tx.into_data();
// log::info!("{:?}", tx); // 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 tx_memo: Memo = Memo::Empty;
let mut contacts = vec![];
if let Some(transparent_bundle) = tx.transparent_bundle() { if let Some(transparent_bundle) = tx.transparent_bundle() {
for output in transparent_bundle.vout.iter() { for output in transparent_bundle.vout.iter() {
if let Some(taddr) = output.recipient_address() { if let Some(taddr) = output.recipient_address() {
taddress = encode_transparent_address( taddress = Some(encode_transparent_address(
&network.b58_pubkey_address_prefix(), &network.b58_pubkey_address_prefix(),
&network.b58_script_address_prefix(), &network.b58_script_address_prefix(),
&taddr, &taddr,
); ));
} }
} }
} }
for output in sapling_bundle.shielded_outputs.iter() { if let Some(sapling_bundle) = tx.sapling_bundle() {
let pivk = PreparedIncomingViewingKey::new(&ivk); let mut contact_decoder = ContactDecoder::new(sapling_bundle.shielded_outputs.len());
if let Some((note, pa, memo)) = try_sapling_note_decryption(network, height, &pivk, output) { for output in sapling_bundle.shielded_outputs.iter() {
amount += note.value as i64; // change or self transfer let pivk = PreparedIncomingViewingKey::new(&sapling_ivk);
let _ = contact_decoder.add_memo(&memo); // ignore memo that is not for contacts if let Some((_note, pa, memo)) = try_sapling_note_decryption(network, height, &pivk, output) {
let memo = Memo::try_from(memo)?; let memo = Memo::try_from(memo)?;
if zaddress.is_empty() { if zaddress.is_none() {
zaddress = encode_payment_address(network.hrp_sapling_payment_address(), &pa); zaddress = Some(encode_payment_address(network.hrp_sapling_payment_address(), &pa));
}
if memo != Memo::Empty {
tx_memo = memo;
}
} }
if memo != Memo::Empty { if let Some((_note, pa, memo, ..)) = try_sapling_output_recovery(network, height, &sapling_ovk, output) {
tx_memo = memo; 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) contacts = contact_decoder.finalize()?;
{ }
zaddress = encode_payment_address(network.hrp_sapling_payment_address(), &pa);
let memo = Memo::try_from(memo)?; if let Some(orchard_bundle) = tx.orchard_bundle() {
if memo != Memo::Empty { if let Some((orchard_ivk, orchard_ovk)) = okey {
tx_memo = memo; 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 = let address = zaddress.or(oaddress).or(taddress).unwrap_or(String::new());
// 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 memo = match tx_memo { let memo = match tx_memo {
Memo::Empty => "".to_string(), Memo::Empty => "".to_string(),
@ -138,178 +158,46 @@ pub async fn decode_transaction(
Memo::Future(_) => "Unrecognized".to_string(), Memo::Future(_) => "Unrecognized".to_string(),
Memo::Arbitrary(_) => "Unrecognized".to_string(), Memo::Arbitrary(_) => "Unrecognized".to_string(),
}; };
let contacts = contact_decoder.finalize()?; let tx_details = TransactionDetails {
let tx_info = TransactionInfo {
height: u32::from(height),
timestamp,
index,
id_tx,
account,
address, address,
memo, memo,
amount,
// fee,
contacts, contacts,
}; };
Ok(tx_info) Ok(tx_details)
}
struct DecodeTxParams<'a> {
tx: SyncSender<TransactionInfo>,
client: CompactTxStreamerClient<Channel>,
nf_map: &'a HashMap<(u32, Vec<u8>), u64>,
index: u32,
id_tx: u32,
account: u32,
fvk: ExtendedFullViewingKey,
tx_hash: Vec<u8>,
height: u32,
timestamp: u32,
} }
pub async fn retrieve_tx_info( pub async fn retrieve_tx_info(
coin_type: CoinType, network: &Network,
client: &mut CompactTxStreamerClient<Channel>, client: &mut CompactTxStreamerClient<Channel>,
db_path: &str, db: &DbAdapter,
tx_ids: &[u32], account: u32,
) -> anyhow::Result<()> { height: u32,
let network = { txid: &Hash,
let chain = get_coin_chain(coin_type); ) -> anyhow::Result<TransactionDetails> {
*chain.network() let transaction = fetch_raw_transaction(network, client, height, txid).await?;
}; let tx_details = decode_transaction(network, account, height, transaction, db)?;
let db = DbAdapter::new(coin_type, db_path)?;
let nfs = db.get_nullifiers_raw()?; Ok(tx_details)
let mut nf_map: HashMap<(u32, Vec<u8>), u64> = HashMap::new();
for nf in nfs.iter() {
nf_map.insert((nf.0, nf.2.clone()), nf.1);
}
let mut fvk_cache: HashMap<u32, ExtendedFullViewingKey> = HashMap::new();
let mut decode_tx_params: Vec<DecodeTxParams> = vec![];
let (tx, rx) = mpsc::sync_channel::<TransactionInfo>(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<ContactRef> = 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(())
} }
#[cfg(test)] pub struct GetTransactionDetailRequest {
mod tests { pub account: u32,
use crate::transaction::decode_transaction; pub height: u32,
use crate::{AccountData, connect_lightwalletd, DbAdapter, LWD_URL}; pub id_tx: u32,
use std::collections::HashMap; pub txid: Hash,
use zcash_client_backend::encoding::decode_extended_full_viewing_key; }
use zcash_params::coin::CoinType;
use zcash_primitives::consensus::{Network, Parameters}; #[derive(Serialize, Debug)]
pub struct TransactionDetails {
#[tokio::test] pub address: String,
async fn test_decode_transaction() { pub memo: String,
let tx_hash = pub contacts: Vec<Contact>,
hex::decode("b47da170329dc311b98892eac23e83025f8bb3ce10bb07535698c91fb37e1e54") }
.unwrap();
let mut client = connect_lightwalletd(LWD_URL).await.unwrap(); #[tokio::test]
let db = DbAdapter::new(CoinType::Zcash, "./zec.db").unwrap(); async fn test_get_transaction_details() {
let account = 1; crate::init_test();
let nfs = db.get_nullifiers_raw().unwrap();
let mut nf_map: HashMap<(u32, Vec<u8>), u64> = HashMap::new(); get_transaction_details(0).await.unwrap();
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);
}
} }

View File

@ -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.sapling.as_ref().map(|a| encode_payment_address(self.network.hrp_sapling_payment_address(), a)),
self.orchard.as_ref().map(|a| { self.orchard.as_ref().map(|a| {
let ua = unified::Address(vec![Receiver::Orchard(a.to_raw_address_bytes())]); 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 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()) Ok(unified_address.encode())
} }
@ -77,7 +77,7 @@ pub fn decode_unified_address(network: &Network, ua: &str) -> anyhow::Result<Dec
sapling: None, sapling: None,
orchard: None orchard: None
}; };
let network = network2network(network); let network = network.address_network().unwrap();
let (a_network, ua) = unified::Address::decode(ua)?; let (a_network, ua) = unified::Address::decode(ua)?;
if network != a_network { if network != a_network {
anyhow::bail!("Invalid network") anyhow::bail!("Invalid network")
@ -101,6 +101,7 @@ pub fn decode_unified_address(network: &Network, ua: &str) -> anyhow::Result<Dec
Ok(decoded_ua) Ok(decoded_ua)
} }
fn network2network(n: &Network) -> zcash_address::Network { n.address_network().unwrap() } pub fn orchard_as_unified(network: &Network, address: &Address) -> ZcashAddress {
let unified_address = unified::Address(vec![Receiver::Orchard(address.to_raw_address_bytes())]);
// u1pncsxa8jt7aq37r8uvhjrgt7sv8a665hdw44rqa28cd9t6qqmktzwktw772nlle6skkkxwmtzxaan3slntqev03g70tzpky3c58hfgvfjkcky255cwqgfuzdjcktfl7pjalt5sl33se75pmga09etn9dplr98eq2g8cgmvgvx6jx2a2xhy39x96c6rumvlyt35whml87r064qdzw30e ZcashAddress::from_unified(network.address_network().unwrap(), unified_address)
}