zcash-sync/src/transaction.rs

316 lines
9.7 KiB
Rust
Raw Normal View History

2021-11-11 17:39:50 -08:00
use crate::contact::{Contact, ContactDecoder};
2022-06-08 05:48:16 -07:00
// use crate::wallet::decode_memo;
use crate::api::payment::decode_memo;
2022-03-07 06:47:06 -08:00
use crate::{CompactTxStreamerClient, DbAdapter, TxFilter};
2022-06-07 09:58:24 -07:00
use anyhow::anyhow;
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;
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-06-07 09:58:24 -07:00
use zcash_params::coin::{get_branch, get_coin_chain, CoinType};
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;
use zcash_primitives::sapling::note_encryption::{PreparedIncomingViewingKey, try_sapling_note_decryption, try_sapling_output_recovery};
2021-07-16 01:42:29 -07:00
use zcash_primitives::transaction::Transaction;
use zcash_primitives::zip32::ExtendedFullViewingKey;
2021-07-09 22:44:13 -07:00
#[derive(Debug)]
pub struct TransactionInfo {
2021-09-08 07:10:22 -07:00
height: u32,
2022-04-15 23:23:50 -07:00
timestamp: u32,
2021-08-10 04:51:39 -07:00
index: u32, // index of tx in block
id_tx: u32, // id of tx in db
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,
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,
fvk: &ExtendedFullViewingKey,
2021-07-16 01:42:29 -07:00
tx_hash: &[u8],
height: u32,
2022-04-15 23:23:50 -07:00
timestamp: u32,
2021-08-10 04:51:39 -07:00
index: u32,
2021-07-16 01:42:29 -07:00
) -> anyhow::Result<TransactionInfo> {
2022-06-08 05:48:16 -07:00
let consensus_branch_id = get_branch(network, 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.recipient_address() {
2022-03-15 01:04:37 -07:00
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() {
let pivk = PreparedIncomingViewingKey::new(&ivk);
if let Some((note, pa, memo)) = try_sapling_note_decryption(network, height, &pivk, output) {
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-06-08 05:48:16 -07: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),
2022-04-15 23:23:50 -07:00
timestamp,
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,
2022-04-15 23:23:50 -07:00
timestamp: u32,
2021-07-31 00:34:57 -07:00
}
pub async fn retrieve_tx_info(
2022-03-07 06:47:06 -08:00
coin_type: CoinType,
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);
2022-06-08 05:48:16 -07:00
*chain.network()
2022-03-07 06:47:06 -08:00
};
2022-04-19 09:47:08 -07:00
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);
}
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() {
2022-04-15 23:23:50 -07:00
let (account, height, timestamp, 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)
.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,
2022-04-15 23:23:50 -07:00
timestamp,
2021-07-31 00:34:57 -07:00
};
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,
2022-04-15 23:23:50 -07:00
p.timestamp,
2021-08-16 06:07:04 -07:00
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)?;
2022-06-07 09:58:24 -07:00
let z_msg = decode_memo(
2022-09-05 08:05:55 -07:00
tx_info.id_tx,
2022-06-07 09:58:24 -07:00
&tx_info.memo,
&tx_info.address,
tx_info.timestamp,
tx_info.height,
);
2022-04-17 01:09:25 -07:00
if !z_msg.is_empty() {
db.store_message(tx_info.account, &z_msg)?;
}
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;
use crate::{AccountData, connect_lightwalletd, DbAdapter, LWD_URL};
2021-07-11 08:42:52 -07:00
use std::collections::HashMap;
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
2022-03-07 06:47:06 -08:00
use zcash_params::coin::CoinType;
2022-06-07 09:58:24 -07:00
use zcash_primitives::consensus::{Network, Parameters};
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-04-19 09:47:08 -07: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);
}
}
let AccountData { fvk, .. } = db.get_account_info(account).unwrap();
2022-06-07 09:58:24 -07:00
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();
2021-07-09 22:44:13 -07:00
println!("{:?}", tx_info);
}
}