2022-11-15 02:21:47 -08:00
|
|
|
use crate::api::recipient::decode_memo;
|
2021-11-11 17:39:50 -08:00
|
|
|
use crate::contact::{Contact, ContactDecoder};
|
2022-11-06 04:50:51 -08:00
|
|
|
use crate::unified::orchard_as_unified;
|
2022-11-05 18:49:17 -07:00
|
|
|
use crate::{AccountData, CoinConfig, CompactTxStreamerClient, DbAdapter, Hash, TxFilter};
|
2022-11-05 19:55:53 -07:00
|
|
|
use orchard::keys::{FullViewingKey, IncomingViewingKey, OutgoingViewingKey, Scope};
|
2022-11-05 18:49:17 -07:00
|
|
|
use orchard::note_encryption::OrchardDomain;
|
2022-11-06 04:50:51 -08:00
|
|
|
use serde::Serialize;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::convert::TryFrom;
|
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-11-05 18:49:17 -07:00
|
|
|
use zcash_note_encryption::{try_note_decryption, try_output_recovery_with_ovk};
|
|
|
|
use zcash_params::coin::get_branch;
|
2022-03-07 06:47:06 -08:00
|
|
|
use zcash_primitives::consensus::{BlockHeight, Network, Parameters};
|
2022-11-05 18:49:17 -07:00
|
|
|
use zcash_primitives::memo::{Memo, MemoBytes};
|
2022-11-06 04:50:51 -08:00
|
|
|
use zcash_primitives::sapling::note_encryption::{
|
|
|
|
try_sapling_note_decryption, try_sapling_output_recovery, PreparedIncomingViewingKey,
|
|
|
|
};
|
2022-11-05 19:55:53 -07:00
|
|
|
use zcash_primitives::sapling::SaplingIvk;
|
2021-07-16 01:42:29 -07:00
|
|
|
use zcash_primitives::transaction::Transaction;
|
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
|
|
|
}
|
|
|
|
|
2022-11-05 18:49:17 -07:00
|
|
|
pub async fn get_transaction_details(coin: u8) -> anyhow::Result<()> {
|
|
|
|
let c = CoinConfig::get(coin);
|
|
|
|
let network = c.chain.network();
|
|
|
|
let mut client = c.connect_lwd().await?;
|
2022-11-05 19:55:53 -07:00
|
|
|
let mut keys = HashMap::new();
|
|
|
|
|
|
|
|
let reqs = {
|
|
|
|
let db = c.db.as_ref().unwrap();
|
|
|
|
let db = db.lock().unwrap();
|
|
|
|
let reqs = db.get_txid_without_memo()?;
|
|
|
|
for req in reqs.iter() {
|
2022-11-15 02:21:47 -08:00
|
|
|
if !keys.contains_key(&req.account) {
|
|
|
|
let decryption_keys = get_decryption_keys(network, req.account, &db)?;
|
|
|
|
keys.insert(req.account, decryption_keys);
|
|
|
|
}
|
2022-11-05 19:55:53 -07:00
|
|
|
}
|
|
|
|
reqs
|
|
|
|
// Make sure we don't hold a mutex across await
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut details = vec![];
|
|
|
|
for req in reqs.iter() {
|
2022-11-15 02:21:47 -08:00
|
|
|
let tx_details = retrieve_tx_info(network, &mut client, req, &keys[&req.account]).await?;
|
2022-11-05 18:49:17 -07:00
|
|
|
log::info!("{:?}", tx_details);
|
2022-11-05 19:55:53 -07:00
|
|
|
details.push(tx_details);
|
|
|
|
}
|
|
|
|
|
|
|
|
let db = c.db.as_ref().unwrap();
|
|
|
|
let db = db.lock().unwrap();
|
|
|
|
for tx_details in details.iter() {
|
|
|
|
db.update_transaction_with_memo(tx_details)?;
|
2022-11-05 18:49:17 -07:00
|
|
|
for c in tx_details.contacts.iter() {
|
|
|
|
db.store_contact(c, false)?;
|
|
|
|
}
|
2022-11-15 02:21:47 -08:00
|
|
|
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)?;
|
|
|
|
}
|
2022-11-05 18:49:17 -07:00
|
|
|
}
|
2022-11-05 19:55:53 -07:00
|
|
|
|
2022-11-05 18:49:17 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-07-09 22:44:13 -07:00
|
|
|
|
2022-11-06 04:50:51 -08:00
|
|
|
async fn fetch_raw_transaction(
|
|
|
|
network: &Network,
|
|
|
|
client: &mut CompactTxStreamerClient<Channel>,
|
|
|
|
height: u32,
|
|
|
|
txid: &Hash,
|
|
|
|
) -> anyhow::Result<Transaction> {
|
2022-11-05 18:49:17 -07:00
|
|
|
let consensus_branch_id = get_branch(network, height);
|
2021-07-09 22:44:13 -07:00
|
|
|
let tx_filter = TxFilter {
|
|
|
|
block: None,
|
|
|
|
index: 0,
|
2022-11-05 18:49:17 -07:00
|
|
|
hash: txid.to_vec(), // only hash is supported
|
2021-07-09 22:44:13 -07:00
|
|
|
};
|
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)?;
|
2022-11-05 18:49:17 -07:00
|
|
|
Ok(tx)
|
|
|
|
}
|
|
|
|
|
2022-11-05 19:55:53 -07:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct DecryptionKeys {
|
|
|
|
sapling_keys: (SaplingIvk, zcash_primitives::keys::OutgoingViewingKey),
|
2022-11-06 04:50:51 -08:00
|
|
|
orchard_keys: Option<(IncomingViewingKey, OutgoingViewingKey)>,
|
2022-11-05 19:55:53 -07:00
|
|
|
}
|
|
|
|
|
2022-11-05 18:49:17 -07:00
|
|
|
pub fn decode_transaction(
|
|
|
|
network: &Network,
|
2022-11-15 02:21:47 -08:00
|
|
|
account: u32,
|
2022-11-05 18:49:17 -07:00
|
|
|
height: u32,
|
2022-11-15 02:21:47 -08:00
|
|
|
timestamp: u32,
|
2022-11-05 19:55:53 -07:00
|
|
|
id_tx: u32,
|
2022-11-05 18:49:17 -07:00
|
|
|
tx: Transaction,
|
2022-11-05 19:55:53 -07:00
|
|
|
decryption_keys: &DecryptionKeys,
|
2022-11-05 18:49:17 -07:00
|
|
|
) -> anyhow::Result<TransactionDetails> {
|
2022-11-05 19:55:53 -07:00
|
|
|
let (sapling_ivk, sapling_ovk) = decryption_keys.sapling_keys.clone();
|
2021-07-09 22:44:13 -07:00
|
|
|
|
2022-11-15 02:21:47 -08:00
|
|
|
let block_height = BlockHeight::from_u32(height);
|
2022-11-05 18:49:17 -07:00
|
|
|
let mut taddress: Option<String> = None;
|
|
|
|
let mut zaddress: Option<String> = None;
|
|
|
|
let mut oaddress: Option<String> = None;
|
2022-03-14 19:40:08 -07:00
|
|
|
|
|
|
|
let tx = tx.into_data();
|
2022-03-15 01:04:37 -07:00
|
|
|
// log::info!("{:?}", tx);
|
2021-09-08 07:10:22 -07:00
|
|
|
|
2021-09-11 06:55:32 -07:00
|
|
|
let mut tx_memo: Memo = Memo::Empty;
|
2022-11-05 18:49:17 -07:00
|
|
|
let mut contacts = vec![];
|
2022-11-15 02:21:47 -08:00
|
|
|
let mut incoming = true;
|
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() {
|
2022-10-21 21:01:29 -07:00
|
|
|
if let Some(taddr) = output.recipient_address() {
|
2022-11-05 18:49:17 -07:00
|
|
|
taddress = Some(encode_transparent_address(
|
2022-03-15 01:04:37 -07:00
|
|
|
&network.b58_pubkey_address_prefix(),
|
|
|
|
&network.b58_script_address_prefix(),
|
|
|
|
&taddr,
|
2022-11-05 18:49:17 -07:00
|
|
|
));
|
2022-03-15 01:04:37 -07:00
|
|
|
}
|
2021-09-25 17:00:04 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-05 18:49:17 -07:00
|
|
|
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);
|
2022-11-06 04:50:51 -08:00
|
|
|
if let Some((_note, pa, memo)) =
|
2022-11-15 02:21:47 -08:00
|
|
|
try_sapling_note_decryption(network, block_height, &pivk, output)
|
2022-11-06 04:50:51 -08:00
|
|
|
{
|
2022-11-05 18:49:17 -07:00
|
|
|
let memo = Memo::try_from(memo)?;
|
|
|
|
if zaddress.is_none() {
|
2022-11-06 04:50:51 -08:00
|
|
|
zaddress = Some(encode_payment_address(
|
|
|
|
network.hrp_sapling_payment_address(),
|
|
|
|
&pa,
|
|
|
|
));
|
2022-11-05 18:49:17 -07:00
|
|
|
}
|
|
|
|
if memo != Memo::Empty {
|
|
|
|
tx_memo = memo;
|
|
|
|
}
|
2021-07-09 22:44:13 -07:00
|
|
|
}
|
2022-11-06 04:50:51 -08:00
|
|
|
if let Some((_note, pa, memo, ..)) =
|
2022-11-15 02:21:47 -08:00
|
|
|
try_sapling_output_recovery(network, block_height, &sapling_ovk, output)
|
2022-11-06 04:50:51 -08:00
|
|
|
{
|
2022-11-05 18:49:17 -07:00
|
|
|
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
|
2022-11-06 04:50:51 -08:00
|
|
|
zaddress = Some(encode_payment_address(
|
|
|
|
network.hrp_sapling_payment_address(),
|
|
|
|
&pa,
|
|
|
|
));
|
2022-11-05 18:49:17 -07:00
|
|
|
let memo = Memo::try_from(memo)?;
|
|
|
|
if memo != Memo::Empty {
|
|
|
|
tx_memo = memo;
|
2022-11-15 02:21:47 -08:00
|
|
|
incoming = false;
|
2022-11-05 18:49:17 -07:00
|
|
|
}
|
2021-09-11 06:55:32 -07:00
|
|
|
}
|
2021-07-09 22:44:13 -07:00
|
|
|
}
|
2022-11-05 18:49:17 -07:00
|
|
|
contacts = contact_decoder.finalize()?;
|
2021-07-09 22:44:13 -07:00
|
|
|
}
|
|
|
|
|
2022-11-05 18:49:17 -07:00
|
|
|
if let Some(orchard_bundle) = tx.orchard_bundle() {
|
2022-11-05 19:55:53 -07:00
|
|
|
if let Some((orchard_ivk, orchard_ovk)) = decryption_keys.orchard_keys.clone() {
|
2022-11-05 18:49:17 -07:00
|
|
|
for action in orchard_bundle.actions().iter() {
|
|
|
|
let domain = OrchardDomain::for_action(action);
|
2022-11-06 04:50:51 -08:00
|
|
|
if let Some((_note, pa, memo)) = try_note_decryption(&domain, &orchard_ivk, action)
|
|
|
|
{
|
2022-11-05 18:49:17 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2022-11-06 04:50:51 -08:00
|
|
|
if let Some((_note, pa, memo, ..)) = try_output_recovery_with_ovk(
|
|
|
|
&domain,
|
|
|
|
&orchard_ovk,
|
|
|
|
action,
|
|
|
|
action.cv_net(),
|
|
|
|
&action.encrypted_note().out_ciphertext,
|
|
|
|
) {
|
2022-11-05 18:49:17 -07:00
|
|
|
let memo = Memo::try_from(MemoBytes::from_bytes(&memo)?)?;
|
|
|
|
oaddress = Some(orchard_as_unified(network, &pa).encode());
|
|
|
|
if memo != Memo::Empty {
|
|
|
|
tx_memo = memo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-09 22:44:13 -07:00
|
|
|
|
2022-11-05 18:49:17 -07:00
|
|
|
let address = zaddress.or(oaddress).or(taddress).unwrap_or(String::new());
|
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(),
|
|
|
|
};
|
2022-11-05 18:49:17 -07:00
|
|
|
let tx_details = TransactionDetails {
|
2022-11-15 02:21:47 -08:00
|
|
|
account,
|
2022-11-05 19:55:53 -07:00
|
|
|
id_tx,
|
2021-07-09 22:44:13 -07:00
|
|
|
address,
|
2022-11-15 02:21:47 -08:00
|
|
|
height,
|
|
|
|
timestamp,
|
2021-07-18 08:57:43 -07:00
|
|
|
memo,
|
2022-11-15 02:21:47 -08:00
|
|
|
incoming,
|
2021-09-08 07:10:22 -07:00
|
|
|
contacts,
|
2021-07-09 22:44:13 -07:00
|
|
|
};
|
|
|
|
|
2022-11-05 18:49:17 -07:00
|
|
|
Ok(tx_details)
|
2021-07-31 00:34:57 -07:00
|
|
|
}
|
|
|
|
|
2022-11-06 04:50:51 -08:00
|
|
|
fn get_decryption_keys(
|
|
|
|
network: &Network,
|
|
|
|
account: u32,
|
|
|
|
db: &DbAdapter,
|
|
|
|
) -> anyhow::Result<DecryptionKeys> {
|
2022-11-05 19:55:53 -07:00
|
|
|
let AccountData { fvk, .. } = db.get_account_info(account)?;
|
2022-11-06 04:50:51 -08:00
|
|
|
let fvk =
|
|
|
|
decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &fvk)
|
|
|
|
.unwrap();
|
2022-11-05 19:55:53 -07:00
|
|
|
let (sapling_ivk, sapling_ovk) = (fvk.fvk.vk.ivk(), 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 decryption_keys = DecryptionKeys {
|
|
|
|
sapling_keys: (sapling_ivk, sapling_ovk),
|
|
|
|
orchard_keys: okey,
|
|
|
|
};
|
|
|
|
Ok(decryption_keys)
|
|
|
|
}
|
|
|
|
|
2021-07-30 01:11:44 -07:00
|
|
|
pub async fn retrieve_tx_info(
|
2022-11-05 18:49:17 -07:00
|
|
|
network: &Network,
|
2021-07-30 01:11:44 -07:00
|
|
|
client: &mut CompactTxStreamerClient<Channel>,
|
2022-11-15 02:21:47 -08:00
|
|
|
req: &GetTransactionDetailRequest,
|
2022-11-06 04:50:51 -08:00
|
|
|
decryption_keys: &DecryptionKeys,
|
2022-11-05 18:49:17 -07:00
|
|
|
) -> anyhow::Result<TransactionDetails> {
|
2022-11-15 02:21:47 -08:00
|
|
|
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,
|
|
|
|
)?;
|
2021-07-31 00:34:57 -07:00
|
|
|
|
2022-11-05 18:49:17 -07:00
|
|
|
Ok(tx_details)
|
|
|
|
}
|
2021-07-31 00:34:57 -07:00
|
|
|
|
2022-11-05 18:49:17 -07:00
|
|
|
pub struct GetTransactionDetailRequest {
|
|
|
|
pub account: u32,
|
|
|
|
pub height: u32,
|
2022-11-15 02:21:47 -08:00
|
|
|
pub timestamp: u32,
|
2022-11-05 18:49:17 -07:00
|
|
|
pub id_tx: u32,
|
|
|
|
pub txid: Hash,
|
|
|
|
}
|
2021-07-09 22:44:13 -07:00
|
|
|
|
2022-11-05 18:49:17 -07:00
|
|
|
#[derive(Serialize, Debug)]
|
|
|
|
pub struct TransactionDetails {
|
2022-11-15 02:21:47 -08:00
|
|
|
pub account: u32,
|
2022-11-05 19:55:53 -07:00
|
|
|
pub id_tx: u32,
|
2022-11-15 02:21:47 -08:00
|
|
|
pub height: u32,
|
|
|
|
pub timestamp: u32,
|
2022-11-05 18:49:17 -07:00
|
|
|
pub address: String,
|
|
|
|
pub memo: String,
|
2022-11-15 02:21:47 -08:00
|
|
|
pub incoming: bool,
|
2022-11-05 18:49:17 -07:00
|
|
|
pub contacts: Vec<Contact>,
|
2021-07-18 08:57:43 -07:00
|
|
|
}
|
|
|
|
|
2022-11-05 18:49:17 -07:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_get_transaction_details() {
|
|
|
|
crate::init_test();
|
2021-07-09 22:44:13 -07:00
|
|
|
|
2022-11-05 18:49:17 -07:00
|
|
|
get_transaction_details(0).await.unwrap();
|
2021-07-09 22:44:13 -07:00
|
|
|
}
|