2021-11-11 17:39:50 -08:00
|
|
|
use crate::contact::{Contact, ContactDecoder};
|
2022-03-07 06:47:06 -08:00
|
|
|
use crate::{CompactTxStreamerClient, DbAdapter, TxFilter};
|
2021-08-16 06:07:04 -07:00
|
|
|
use futures::StreamExt;
|
2021-08-10 04:51:39 -07:00
|
|
|
use std::collections::HashMap;
|
2021-07-16 01:42:29 -07:00
|
|
|
use std::convert::TryFrom;
|
2021-08-16 06:07:04 -07:00
|
|
|
use std::sync::mpsc;
|
|
|
|
use std::sync::mpsc::SyncSender;
|
2022-03-14 19:40:08 -07:00
|
|
|
use anyhow::anyhow;
|
2021-07-09 22:44:13 -07:00
|
|
|
use tonic::transport::Channel;
|
|
|
|
use tonic::Request;
|
2021-07-16 01:42:29 -07:00
|
|
|
use zcash_client_backend::encoding::{
|
|
|
|
decode_extended_full_viewing_key, encode_payment_address, encode_transparent_address,
|
|
|
|
};
|
2022-03-07 06:47:06 -08:00
|
|
|
use zcash_primitives::consensus::{BlockHeight, Network, Parameters};
|
2021-09-11 06:55:32 -07:00
|
|
|
use zcash_primitives::memo::Memo;
|
2021-07-16 01:42:29 -07:00
|
|
|
use zcash_primitives::sapling::note_encryption::{
|
|
|
|
try_sapling_note_decryption, try_sapling_output_recovery,
|
|
|
|
};
|
|
|
|
use zcash_primitives::transaction::Transaction;
|
2021-07-30 01:11:44 -07:00
|
|
|
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
2022-03-14 19:40:08 -07:00
|
|
|
use zcash_params::coin::{CoinType, get_coin_chain, get_branch};
|
2021-07-09 22:44:13 -07:00
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct TransactionInfo {
|
2021-09-08 07:10:22 -07:00
|
|
|
height: u32,
|
2021-08-10 04:51:39 -07:00
|
|
|
index: u32, // index of tx in block
|
|
|
|
id_tx: u32, // id of tx in db
|
2022-02-25 06:26:36 -08:00
|
|
|
pub account: u32,
|
2021-07-09 22:44:13 -07:00
|
|
|
pub address: String,
|
2021-07-18 08:57:43 -07:00
|
|
|
pub memo: String,
|
2022-02-25 06:26:36 -08:00
|
|
|
pub amount: i64,
|
2022-03-14 19:40:08 -07:00
|
|
|
// pub fee: u64,
|
2021-09-08 07:10:22 -07:00
|
|
|
pub contacts: Vec<Contact>,
|
2021-07-09 22:44:13 -07:00
|
|
|
}
|
|
|
|
|
2021-07-18 08:57:43 -07:00
|
|
|
#[derive(Debug)]
|
2021-09-08 07:10:22 -07:00
|
|
|
pub struct ContactRef {
|
|
|
|
pub height: u32,
|
|
|
|
pub index: u32,
|
|
|
|
pub contact: Contact,
|
2021-07-18 08:57:43 -07:00
|
|
|
}
|
|
|
|
|
2021-07-16 01:42:29 -07:00
|
|
|
pub async fn decode_transaction(
|
2022-03-07 06:47:06 -08:00
|
|
|
network: &Network,
|
2021-07-16 01:42:29 -07:00
|
|
|
client: &mut CompactTxStreamerClient<Channel>,
|
|
|
|
nfs: &HashMap<(u32, Vec<u8>), u64>,
|
2021-07-31 00:34:57 -07:00
|
|
|
id_tx: u32,
|
2021-07-16 01:42:29 -07:00
|
|
|
account: u32,
|
2021-07-30 01:11:44 -07:00
|
|
|
fvk: &ExtendedFullViewingKey,
|
2021-07-16 01:42:29 -07:00
|
|
|
tx_hash: &[u8],
|
|
|
|
height: u32,
|
2021-08-10 04:51:39 -07:00
|
|
|
index: u32,
|
2021-07-16 01:42:29 -07:00
|
|
|
) -> anyhow::Result<TransactionInfo> {
|
2022-03-14 19:40:08 -07:00
|
|
|
let consensus_branch_id = get_branch(network, u32::from(height));
|
2021-07-09 22:44:13 -07:00
|
|
|
let ivk = fvk.fvk.vk.ivk();
|
|
|
|
let ovk = fvk.fvk.ovk;
|
|
|
|
|
|
|
|
let tx_filter = TxFilter {
|
|
|
|
block: None,
|
|
|
|
index: 0,
|
|
|
|
hash: tx_hash.to_vec(), // only hash is supported
|
|
|
|
};
|
2021-07-16 01:42:29 -07:00
|
|
|
let raw_tx = client
|
|
|
|
.get_transaction(Request::new(tx_filter))
|
|
|
|
.await?
|
|
|
|
.into_inner();
|
2022-03-14 19:40:08 -07:00
|
|
|
let tx = Transaction::read(&*raw_tx.data, consensus_branch_id)?;
|
2021-07-09 22:44:13 -07:00
|
|
|
|
|
|
|
let height = BlockHeight::from_u32(height);
|
|
|
|
let mut amount = 0i64;
|
2022-03-07 21:12:34 -08:00
|
|
|
let mut taddress = String::new();
|
|
|
|
let mut zaddress = String::new();
|
2022-03-14 19:40:08 -07:00
|
|
|
|
|
|
|
let tx = tx.into_data();
|
2022-03-15 01:04:37 -07:00
|
|
|
// log::info!("{:?}", tx);
|
2022-03-14 19:40:08 -07:00
|
|
|
let sapling_bundle = tx.sapling_bundle().ok_or(anyhow!("No sapling bundle"))?;
|
|
|
|
for spend in sapling_bundle.shielded_spends.iter() {
|
2021-07-09 22:44:13 -07:00
|
|
|
let nf = spend.nullifier.to_vec();
|
2021-07-11 08:42:52 -07:00
|
|
|
if let Some(&v) = nfs.get(&(account, nf)) {
|
2021-07-09 22:44:13 -07:00
|
|
|
amount -= v as i64;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-15 01:04:37 -07:00
|
|
|
let mut contact_decoder = ContactDecoder::new(sapling_bundle.shielded_outputs.len());
|
2021-09-08 07:10:22 -07:00
|
|
|
|
2021-09-11 06:55:32 -07:00
|
|
|
let mut tx_memo: Memo = Memo::Empty;
|
2021-07-16 01:42:29 -07:00
|
|
|
|
2022-03-15 01:04:37 -07:00
|
|
|
if let Some(transparent_bundle) = tx.transparent_bundle() {
|
|
|
|
for output in transparent_bundle.vout.iter() {
|
|
|
|
if let Some(taddr) = output.script_pubkey.address() {
|
|
|
|
taddress = encode_transparent_address(
|
|
|
|
&network.b58_pubkey_address_prefix(),
|
|
|
|
&network.b58_script_address_prefix(),
|
|
|
|
&taddr,
|
|
|
|
);
|
|
|
|
}
|
2021-09-25 17:00:04 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-14 19:40:08 -07:00
|
|
|
for output in sapling_bundle.shielded_outputs.iter() {
|
2022-03-07 06:47:06 -08:00
|
|
|
if let Some((note, pa, memo)) = try_sapling_note_decryption(network, height, &ivk, output)
|
2021-07-16 01:42:29 -07:00
|
|
|
{
|
2021-07-09 22:44:13 -07:00
|
|
|
amount += note.value as i64; // change or self transfer
|
2021-09-11 23:21:34 -07:00
|
|
|
let _ = contact_decoder.add_memo(&memo); // ignore memo that is not for contacts
|
2021-09-11 06:55:32 -07:00
|
|
|
let memo = Memo::try_from(memo)?;
|
2022-03-07 21:12:34 -08:00
|
|
|
if zaddress.is_empty() {
|
|
|
|
zaddress = encode_payment_address(network.hrp_sapling_payment_address(), &pa);
|
2021-09-11 06:55:32 -07:00
|
|
|
}
|
|
|
|
if memo != Memo::Empty {
|
2021-07-09 22:44:13 -07:00
|
|
|
tx_memo = memo;
|
|
|
|
}
|
2021-07-16 01:42:29 -07:00
|
|
|
} else if let Some((_note, pa, memo)) =
|
2022-03-07 06:47:06 -08:00
|
|
|
try_sapling_output_recovery(network, height, &ovk, &output)
|
2021-07-16 01:42:29 -07:00
|
|
|
{
|
2022-03-07 21:12:34 -08:00
|
|
|
zaddress = encode_payment_address(network.hrp_sapling_payment_address(), &pa);
|
2021-09-11 06:55:32 -07:00
|
|
|
let memo = Memo::try_from(memo)?;
|
|
|
|
if memo != Memo::Empty {
|
|
|
|
tx_memo = memo;
|
|
|
|
}
|
2021-07-09 22:44:13 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-14 19:40:08 -07:00
|
|
|
// let fee =
|
|
|
|
// u64::from() +
|
|
|
|
// u64::from(tx.sapling_bundle().unwrap().value_balance);
|
2021-07-09 22:44:13 -07:00
|
|
|
|
2022-03-07 21:12:34 -08:00
|
|
|
// 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
|
2022-03-25 19:39:45 -07:00
|
|
|
if zaddress.is_empty() { taddress } else { zaddress };
|
2022-03-07 21:12:34 -08:00
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
let memo = match tx_memo {
|
|
|
|
Memo::Empty => "".to_string(),
|
|
|
|
Memo::Text(text) => text.to_string(),
|
|
|
|
Memo::Future(_) => "Unrecognized".to_string(),
|
|
|
|
Memo::Arbitrary(_) => "Unrecognized".to_string(),
|
|
|
|
};
|
2021-09-08 07:10:22 -07:00
|
|
|
let contacts = contact_decoder.finalize()?;
|
2021-07-09 22:44:13 -07:00
|
|
|
let tx_info = TransactionInfo {
|
2021-09-08 07:10:22 -07:00
|
|
|
height: u32::from(height),
|
2021-08-10 04:51:39 -07:00
|
|
|
index,
|
2021-07-31 00:34:57 -07:00
|
|
|
id_tx,
|
|
|
|
account,
|
2021-07-09 22:44:13 -07:00
|
|
|
address,
|
2021-07-18 08:57:43 -07:00
|
|
|
memo,
|
2021-07-09 22:44:13 -07:00
|
|
|
amount,
|
2022-03-14 19:40:08 -07:00
|
|
|
// fee,
|
2021-09-08 07:10:22 -07:00
|
|
|
contacts,
|
2021-07-09 22:44:13 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(tx_info)
|
|
|
|
}
|
|
|
|
|
2021-07-31 00:34:57 -07:00
|
|
|
struct DecodeTxParams<'a> {
|
|
|
|
tx: SyncSender<TransactionInfo>,
|
|
|
|
client: CompactTxStreamerClient<Channel>,
|
|
|
|
nf_map: &'a HashMap<(u32, Vec<u8>), u64>,
|
2021-08-10 04:51:39 -07:00
|
|
|
index: u32,
|
2021-07-31 00:34:57 -07:00
|
|
|
id_tx: u32,
|
|
|
|
account: u32,
|
|
|
|
fvk: ExtendedFullViewingKey,
|
|
|
|
tx_hash: Vec<u8>,
|
|
|
|
height: u32,
|
|
|
|
}
|
|
|
|
|
2021-07-30 01:11:44 -07:00
|
|
|
pub async fn retrieve_tx_info(
|
2022-03-07 06:47:06 -08:00
|
|
|
coin_type: CoinType,
|
2021-07-30 01:11:44 -07:00
|
|
|
client: &mut CompactTxStreamerClient<Channel>,
|
|
|
|
db_path: &str,
|
|
|
|
tx_ids: &[u32],
|
|
|
|
) -> anyhow::Result<()> {
|
2022-03-07 06:47:06 -08:00
|
|
|
let network = {
|
|
|
|
let chain = get_coin_chain(coin_type);
|
|
|
|
chain.network().clone()
|
|
|
|
};
|
|
|
|
let db = DbAdapter::new(coin_type, db_path)?;
|
2021-09-02 19:56:10 -07:00
|
|
|
|
2021-07-11 08:42:52 -07:00
|
|
|
let nfs = db.get_nullifiers_raw()?;
|
|
|
|
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);
|
|
|
|
}
|
2021-07-30 01:11:44 -07:00
|
|
|
let mut fvk_cache: HashMap<u32, ExtendedFullViewingKey> = HashMap::new();
|
2021-07-31 00:34:57 -07:00
|
|
|
let mut decode_tx_params: Vec<DecodeTxParams> = vec![];
|
|
|
|
let (tx, rx) = mpsc::sync_channel::<TransactionInfo>(4);
|
2021-08-10 04:51:39 -07:00
|
|
|
for (index, &id_tx) in tx_ids.iter().enumerate() {
|
2021-07-30 01:11:44 -07:00
|
|
|
let (account, height, tx_hash, ivk) = db.get_txhash(id_tx)?;
|
2021-07-31 00:34:57 -07:00
|
|
|
let fvk: &ExtendedFullViewingKey = fvk_cache.entry(account).or_insert_with(|| {
|
2022-03-07 06:47:06 -08:00
|
|
|
decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &ivk)
|
2021-07-30 01:11:44 -07:00
|
|
|
.unwrap()
|
|
|
|
.unwrap()
|
|
|
|
});
|
2021-07-31 00:34:57 -07:00
|
|
|
let params = DecodeTxParams {
|
|
|
|
tx: tx.clone(),
|
|
|
|
client: client.clone(),
|
|
|
|
nf_map: &nf_map,
|
2021-08-10 04:51:39 -07:00
|
|
|
index: index as u32,
|
2021-07-31 00:34:57 -07:00
|
|
|
id_tx,
|
|
|
|
account,
|
|
|
|
fvk: fvk.clone(),
|
|
|
|
tx_hash: tx_hash.clone(),
|
|
|
|
height,
|
|
|
|
};
|
|
|
|
decode_tx_params.push(params);
|
|
|
|
}
|
|
|
|
|
|
|
|
let res = tokio_stream::iter(decode_tx_params).for_each_concurrent(None, |mut p| async move {
|
2021-08-16 06:07:04 -07:00
|
|
|
if let Ok(tx_info) = decode_transaction(
|
2022-03-07 06:47:06 -08:00
|
|
|
&network,
|
2021-08-16 06:07:04 -07:00
|
|
|
&mut p.client,
|
|
|
|
p.nf_map,
|
|
|
|
p.id_tx,
|
|
|
|
p.account,
|
|
|
|
&p.fvk,
|
|
|
|
&p.tx_hash,
|
|
|
|
p.height,
|
|
|
|
p.index,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
{
|
2021-07-31 00:34:57 -07:00
|
|
|
p.tx.send(tx_info).unwrap();
|
|
|
|
drop(p.tx);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let f = tokio::spawn(async move {
|
2021-09-08 07:10:22 -07:00
|
|
|
let mut contacts: Vec<ContactRef> = vec![];
|
2021-07-31 00:34:57 -07:00
|
|
|
while let Ok(tx_info) = rx.recv() {
|
2021-09-08 07:10:22 -07:00
|
|
|
for c in tx_info.contacts.iter() {
|
|
|
|
contacts.push(ContactRef {
|
|
|
|
height: tx_info.height,
|
|
|
|
index: tx_info.index,
|
|
|
|
contact: c.clone(),
|
|
|
|
});
|
2021-07-18 08:57:43 -07:00
|
|
|
}
|
2021-07-31 00:34:57 -07:00
|
|
|
db.store_tx_metadata(tx_info.id_tx, &tx_info)?;
|
2021-07-18 08:57:43 -07:00
|
|
|
}
|
2021-08-10 04:51:39 -07:00
|
|
|
contacts.sort_by(|a, b| a.index.cmp(&b.index));
|
2021-09-08 07:10:22 -07:00
|
|
|
for cref in contacts.iter() {
|
|
|
|
db.store_contact(&cref.contact, false)?;
|
2021-08-10 04:51:39 -07:00
|
|
|
}
|
2021-07-31 00:34:57 -07:00
|
|
|
|
|
|
|
Ok::<_, anyhow::Error>(())
|
|
|
|
});
|
|
|
|
|
|
|
|
res.await;
|
|
|
|
drop(tx);
|
|
|
|
f.await??;
|
2021-07-09 22:44:13 -07:00
|
|
|
|
|
|
|
Ok(())
|
2021-07-18 08:57:43 -07:00
|
|
|
}
|
|
|
|
|
2021-07-09 22:44:13 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use crate::transaction::decode_transaction;
|
2022-03-07 06:47:06 -08:00
|
|
|
use crate::{connect_lightwalletd, DbAdapter, LWD_URL};
|
2021-07-11 08:42:52 -07:00
|
|
|
use std::collections::HashMap;
|
2021-07-30 01:11:44 -07:00
|
|
|
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
2022-03-07 06:47:06 -08:00
|
|
|
use zcash_primitives::consensus::{Network, Parameters};
|
|
|
|
use zcash_params::coin::CoinType;
|
2021-07-09 22:44:13 -07:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_decode_transaction() {
|
2021-07-16 01:42:29 -07:00
|
|
|
let tx_hash =
|
|
|
|
hex::decode("b47da170329dc311b98892eac23e83025f8bb3ce10bb07535698c91fb37e1e54")
|
|
|
|
.unwrap();
|
2021-07-09 22:44:13 -07:00
|
|
|
let mut client = connect_lightwalletd(LWD_URL).await.unwrap();
|
2022-03-07 06:47:06 -08:00
|
|
|
let db = DbAdapter::new(CoinType::Zcash, "./zec.db").unwrap();
|
2021-07-09 22:44:13 -07:00
|
|
|
let account = 1;
|
2021-07-11 08:42:52 -07:00
|
|
|
let nfs = db.get_nullifiers_raw().unwrap();
|
|
|
|
let mut nf_map: HashMap<(u32, Vec<u8>), u64> = HashMap::new();
|
|
|
|
for nf in nfs.iter() {
|
|
|
|
if nf.0 == account {
|
|
|
|
nf_map.insert((nf.0, nf.2.clone()), nf.1);
|
|
|
|
}
|
|
|
|
}
|
2021-07-09 22:44:13 -07:00
|
|
|
let fvk = db.get_ivk(account).unwrap();
|
2021-07-30 01:11:44 -07:00
|
|
|
let fvk =
|
2022-03-07 06:47:06 -08:00
|
|
|
decode_extended_full_viewing_key(Network::MainNetwork.hrp_sapling_extended_full_viewing_key(), &fvk)
|
2021-07-30 01:11:44 -07:00
|
|
|
.unwrap()
|
|
|
|
.unwrap();
|
2021-08-16 06:07:04 -07:00
|
|
|
let tx_info =
|
2022-03-07 06:47:06 -08:00
|
|
|
decode_transaction(&Network::MainNetwork, &mut client, &nf_map, 1, account, &fvk, &tx_hash, 1313212, 1)
|
2021-08-16 06:07:04 -07:00
|
|
|
.await
|
|
|
|
.unwrap();
|
2021-07-09 22:44:13 -07:00
|
|
|
println!("{:?}", tx_info);
|
|
|
|
}
|
|
|
|
}
|