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 = 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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
37
src/db.rs
37
src/db.rs
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
|
@ -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![
|
||||||
|
|
32
src/scan.rs
32
src/scan.rs
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue