Get Transaction Details
This commit is contained in:
parent
f222187650
commit
b61772b639
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
|
@ -61,17 +61,14 @@ async fn coin_sync_impl(
|
|||
progress_callback: AMProgressCallback,
|
||||
cancel: &'static std::sync::Mutex<bool>,
|
||||
) -> 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(())
|
||||
|
|
37
src/db.rs
37
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<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>> {
|
||||
// get id_account from fvk
|
||||
// truncate received_notes, sapling_witnesses for account
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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![
|
||||
|
|
32
src/scan.rs
32
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<Network, OrchardDomain, OrchardViewKey,
|
|||
OrchardDecrypter<Network>, 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<bool>,
|
||||
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(())
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Contact>,
|
||||
}
|
||||
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<Channel>,
|
||||
nfs: &HashMap<(u32, Vec<u8>), u64>,
|
||||
id_tx: u32,
|
||||
account: u32,
|
||||
fvk: &ExtendedFullViewingKey,
|
||||
tx_hash: &[u8],
|
||||
height: u32,
|
||||
timestamp: u32,
|
||||
index: u32,
|
||||
) -> anyhow::Result<TransactionInfo> {
|
||||
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<Channel>, height: u32, txid: &Hash) -> anyhow::Result<Transaction> {
|
||||
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<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 mut amount = 0i64;
|
||||
let mut taddress = String::new();
|
||||
let mut zaddress = String::new();
|
||||
let mut taddress: Option<String> = None;
|
||||
let mut zaddress: Option<String> = None;
|
||||
let mut oaddress: Option<String> = 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,
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(&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 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_empty() {
|
||||
zaddress = encode_payment_address(network.hrp_sapling_payment_address(), &pa);
|
||||
if zaddress.is_none() {
|
||||
zaddress = Some(encode_payment_address(network.hrp_sapling_payment_address(), &pa));
|
||||
}
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
contacts = contact_decoder.finalize()?;
|
||||
}
|
||||
|
||||
// let fee =
|
||||
// u64::from() +
|
||||
// u64::from(tx.sapling_bundle().unwrap().value_balance);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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<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,
|
||||
Ok(tx_details)
|
||||
}
|
||||
|
||||
pub async fn retrieve_tx_info(
|
||||
coin_type: CoinType,
|
||||
network: &Network,
|
||||
client: &mut CompactTxStreamerClient<Channel>,
|
||||
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<TransactionDetails> {
|
||||
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<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(())
|
||||
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<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();
|
||||
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<Contact>,
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_transaction_details() {
|
||||
crate::init_test();
|
||||
|
||||
get_transaction_details(0).await.unwrap();
|
||||
}
|
||||
|
|
|
@ -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<Dec
|
|||
sapling: None,
|
||||
orchard: None
|
||||
};
|
||||
let network = network2network(network);
|
||||
let network = network.address_network().unwrap();
|
||||
let (a_network, ua) = unified::Address::decode(ua)?;
|
||||
if network != a_network {
|
||||
anyhow::bail!("Invalid network")
|
||||
|
@ -101,6 +101,7 @@ pub fn decode_unified_address(network: &Network, ua: &str) -> anyhow::Result<Dec
|
|||
Ok(decoded_ua)
|
||||
}
|
||||
|
||||
fn network2network(n: &Network) -> zcash_address::Network { n.address_network().unwrap() }
|
||||
|
||||
// u1pncsxa8jt7aq37r8uvhjrgt7sv8a665hdw44rqa28cd9t6qqmktzwktw772nlle6skkkxwmtzxaan3slntqev03g70tzpky3c58hfgvfjkcky255cwqgfuzdjcktfl7pjalt5sl33se75pmga09etn9dplr98eq2g8cgmvgvx6jx2a2xhy39x96c6rumvlyt35whml87r064qdzw30e
|
||||
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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue