2021-06-17 09:56:20 -07:00
|
|
|
use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
|
|
|
|
use crate::lw_rpc::*;
|
2021-06-21 17:33:13 -07:00
|
|
|
use crate::{NETWORK, advance_tree};
|
2021-06-18 01:17:41 -07:00
|
|
|
use ff::PrimeField;
|
|
|
|
use group::GroupEncoding;
|
|
|
|
use rayon::prelude::*;
|
2021-06-21 17:33:13 -07:00
|
|
|
use tonic::transport::{Channel, Certificate, ClientTlsConfig};
|
2021-06-17 09:56:20 -07:00
|
|
|
use tonic::Request;
|
2021-06-21 17:33:13 -07:00
|
|
|
use zcash_primitives::consensus::{BlockHeight, Parameters, NetworkUpgrade};
|
2021-06-18 01:17:41 -07:00
|
|
|
use zcash_primitives::merkle_tree::{CommitmentTree, IncrementalWitness};
|
|
|
|
use zcash_primitives::sapling::note_encryption::try_sapling_compact_note_decryption;
|
2021-06-21 17:33:13 -07:00
|
|
|
use zcash_primitives::sapling::{Node, Note, PaymentAddress};
|
2021-06-17 09:56:20 -07:00
|
|
|
use zcash_primitives::transaction::components::sapling::CompactOutputDescription;
|
2021-06-21 17:33:13 -07:00
|
|
|
use crate::commitment::{CTree, Witness};
|
2021-06-18 01:17:41 -07:00
|
|
|
use std::time::Instant;
|
|
|
|
use log::info;
|
2021-06-21 17:33:13 -07:00
|
|
|
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
|
|
|
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
2021-06-17 09:56:20 -07:00
|
|
|
|
|
|
|
const MAX_CHUNK: u32 = 50000;
|
2021-06-21 17:33:13 -07:00
|
|
|
// pub const LWD_URL: &str = "https://mainnet.lightwalletd.com:9067";
|
|
|
|
// pub const LWD_URL: &str = "http://lwd.hanh.me:9067";
|
|
|
|
// pub const LWD_URL: &str = "https://lwdv3.zecwallet.co";
|
2021-06-18 01:17:41 -07:00
|
|
|
pub const LWD_URL: &str = "http://127.0.0.1:9067";
|
2021-06-17 09:56:20 -07:00
|
|
|
|
|
|
|
pub async fn get_latest_height(
|
|
|
|
client: &mut CompactTxStreamerClient<Channel>,
|
|
|
|
) -> anyhow::Result<u32> {
|
|
|
|
let chainspec = ChainSpec {};
|
|
|
|
let rep = client.get_latest_block(Request::new(chainspec)).await?;
|
|
|
|
let block_id = rep.into_inner();
|
|
|
|
Ok(block_id.height as u32)
|
|
|
|
}
|
|
|
|
|
|
|
|
/* download [start_height+1, end_height] inclusive */
|
|
|
|
pub async fn download_chain(
|
|
|
|
client: &mut CompactTxStreamerClient<Channel>,
|
|
|
|
start_height: u32,
|
2021-06-18 01:17:41 -07:00
|
|
|
end_height: u32,
|
2021-06-17 09:56:20 -07:00
|
|
|
) -> anyhow::Result<Vec<CompactBlock>> {
|
|
|
|
let mut cbs: Vec<CompactBlock> = Vec::new();
|
|
|
|
let mut s = start_height + 1;
|
|
|
|
while s < end_height {
|
2021-06-18 01:17:41 -07:00
|
|
|
let e = (s + MAX_CHUNK - 1).min(end_height);
|
2021-06-17 09:56:20 -07:00
|
|
|
let range = BlockRange {
|
2021-06-18 01:17:41 -07:00
|
|
|
start: Some(BlockId {
|
|
|
|
height: s as u64,
|
|
|
|
hash: vec![],
|
|
|
|
}),
|
|
|
|
end: Some(BlockId {
|
|
|
|
height: e as u64,
|
|
|
|
hash: vec![],
|
|
|
|
}),
|
2021-06-17 09:56:20 -07:00
|
|
|
};
|
2021-06-18 01:17:41 -07:00
|
|
|
let mut block_stream = client
|
|
|
|
.get_block_range(Request::new(range))
|
|
|
|
.await?
|
|
|
|
.into_inner();
|
2021-06-17 09:56:20 -07:00
|
|
|
while let Some(block) = block_stream.message().await? {
|
|
|
|
cbs.push(block);
|
|
|
|
}
|
|
|
|
s = e + 1;
|
|
|
|
}
|
|
|
|
Ok(cbs)
|
|
|
|
}
|
|
|
|
|
2021-06-18 01:17:41 -07:00
|
|
|
pub struct DecryptNode {
|
2021-06-21 17:33:13 -07:00
|
|
|
fvks: Vec<ExtendedFullViewingKey>,
|
2021-06-17 09:56:20 -07:00
|
|
|
}
|
|
|
|
|
2021-06-18 01:17:41 -07:00
|
|
|
pub struct DecryptedBlock {
|
|
|
|
pub height: u32,
|
|
|
|
pub notes: Vec<DecryptedNote>,
|
|
|
|
pub count_outputs: u32,
|
|
|
|
}
|
|
|
|
|
2021-06-21 17:33:13 -07:00
|
|
|
#[derive(Clone)]
|
2021-06-18 01:17:41 -07:00
|
|
|
pub struct DecryptedNote {
|
2021-06-21 17:33:13 -07:00
|
|
|
pub ivk: ExtendedFullViewingKey,
|
2021-06-18 01:17:41 -07:00
|
|
|
pub note: Note,
|
2021-06-21 17:33:13 -07:00
|
|
|
pub pa: PaymentAddress,
|
|
|
|
pub position: usize,
|
|
|
|
|
|
|
|
pub height: u32,
|
|
|
|
pub txid: Vec<u8>,
|
|
|
|
pub tx_index: usize,
|
|
|
|
pub output_index: usize,
|
2021-06-18 01:17:41 -07:00
|
|
|
}
|
|
|
|
|
2021-06-21 17:33:13 -07:00
|
|
|
fn decrypt_notes(block: &CompactBlock, fvks: &[ExtendedFullViewingKey]) -> DecryptedBlock {
|
2021-06-17 09:56:20 -07:00
|
|
|
let height = BlockHeight::from_u32(block.height as u32);
|
2021-06-18 01:17:41 -07:00
|
|
|
let mut count_outputs = 0u32;
|
|
|
|
let mut notes: Vec<DecryptedNote> = vec![];
|
2021-06-21 17:33:13 -07:00
|
|
|
for (tx_index, vtx) in block.vtx.iter().enumerate() {
|
|
|
|
for (output_index, co) in vtx.outputs.iter().enumerate() {
|
2021-06-17 09:56:20 -07:00
|
|
|
let mut cmu = [0u8; 32];
|
|
|
|
cmu.copy_from_slice(&co.cmu);
|
|
|
|
let cmu = bls12_381::Scalar::from_repr(cmu).unwrap();
|
|
|
|
let mut epk = [0u8; 32];
|
|
|
|
epk.copy_from_slice(&co.epk);
|
|
|
|
let epk = jubjub::ExtendedPoint::from_bytes(&epk).unwrap();
|
|
|
|
let od = CompactOutputDescription {
|
|
|
|
epk,
|
|
|
|
cmu,
|
|
|
|
enc_ciphertext: co.ciphertext.to_vec(),
|
|
|
|
};
|
2021-06-21 17:33:13 -07:00
|
|
|
for fvk in fvks.iter() {
|
|
|
|
let ivk = &fvk.fvk.vk.ivk();
|
|
|
|
if let Some((note, pa)) =
|
2021-06-18 01:17:41 -07:00
|
|
|
try_sapling_compact_note_decryption(&NETWORK, height, ivk, &od)
|
|
|
|
{
|
|
|
|
notes.push(DecryptedNote {
|
2021-06-21 17:33:13 -07:00
|
|
|
ivk: fvk.clone(),
|
2021-06-18 01:17:41 -07:00
|
|
|
note,
|
2021-06-21 17:33:13 -07:00
|
|
|
pa,
|
|
|
|
position: count_outputs as usize,
|
|
|
|
height: block.height as u32,
|
|
|
|
tx_index,
|
|
|
|
txid: vtx.hash.clone(),
|
|
|
|
output_index,
|
2021-06-18 01:17:41 -07:00
|
|
|
});
|
2021-06-17 09:56:20 -07:00
|
|
|
}
|
|
|
|
}
|
2021-06-18 01:17:41 -07:00
|
|
|
count_outputs += 1;
|
2021-06-17 09:56:20 -07:00
|
|
|
}
|
|
|
|
}
|
2021-06-18 01:17:41 -07:00
|
|
|
DecryptedBlock {
|
|
|
|
height: block.height as u32,
|
|
|
|
notes,
|
|
|
|
count_outputs,
|
|
|
|
}
|
2021-06-17 09:56:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl DecryptNode {
|
2021-06-21 17:33:13 -07:00
|
|
|
pub fn new(fvks: Vec<ExtendedFullViewingKey>) -> DecryptNode {
|
|
|
|
DecryptNode { fvks }
|
2021-06-18 01:17:41 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn decrypt_blocks(&self, blocks: &[CompactBlock]) -> Vec<DecryptedBlock> {
|
|
|
|
let mut decrypted_blocks: Vec<DecryptedBlock> = blocks
|
|
|
|
.par_iter()
|
2021-06-21 17:33:13 -07:00
|
|
|
.map(|b| decrypt_notes(b, &self.fvks))
|
2021-06-18 01:17:41 -07:00
|
|
|
.collect();
|
|
|
|
decrypted_blocks.sort_by(|a, b| a.height.cmp(&b.height));
|
|
|
|
decrypted_blocks
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
async fn get_tree_state(client: &mut CompactTxStreamerClient<Channel>, height: u32) -> String {
|
|
|
|
let block_id = BlockId {
|
|
|
|
height: height as u64,
|
|
|
|
hash: vec![],
|
|
|
|
};
|
|
|
|
let rep = client
|
|
|
|
.get_tree_state(Request::new(block_id))
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.into_inner();
|
|
|
|
rep.tree
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Using the IncrementalWitness */
|
|
|
|
#[allow(dead_code)]
|
|
|
|
fn calculate_tree_state_v1(
|
|
|
|
cbs: &[CompactBlock],
|
|
|
|
blocks: &[DecryptedBlock],
|
|
|
|
height: u32,
|
|
|
|
mut tree_state: CommitmentTree<Node>,
|
|
|
|
) -> Vec<IncrementalWitness<Node>> {
|
|
|
|
let mut witnesses: Vec<IncrementalWitness<Node>> = vec![];
|
|
|
|
for (cb, block) in cbs.iter().zip(blocks) {
|
|
|
|
assert_eq!(cb.height as u32, block.height);
|
|
|
|
if block.height < height {
|
|
|
|
continue;
|
|
|
|
} // skip before height
|
|
|
|
let mut notes = block.notes.iter();
|
|
|
|
let mut n = notes.next();
|
2021-06-21 17:33:13 -07:00
|
|
|
let mut i = 0usize;
|
2021-06-18 01:17:41 -07:00
|
|
|
for tx in cb.vtx.iter() {
|
|
|
|
for co in tx.outputs.iter() {
|
|
|
|
let mut cmu = [0u8; 32];
|
|
|
|
cmu.copy_from_slice(&co.cmu);
|
|
|
|
let node = Node::new(cmu);
|
|
|
|
tree_state.append(node).unwrap();
|
|
|
|
for w in witnesses.iter_mut() {
|
|
|
|
w.append(node).unwrap();
|
|
|
|
}
|
|
|
|
if let Some(nn) = n {
|
|
|
|
if i == nn.position {
|
|
|
|
let w = IncrementalWitness::from_tree(&tree_state);
|
|
|
|
witnesses.push(w);
|
|
|
|
n = notes.next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i += 1;
|
|
|
|
}
|
2021-06-17 09:56:20 -07:00
|
|
|
}
|
|
|
|
}
|
2021-06-18 01:17:41 -07:00
|
|
|
// let mut bb: Vec<u8> = vec![];
|
|
|
|
// tree_state.write(&mut bb).unwrap();
|
|
|
|
// hex::encode(bb)
|
|
|
|
|
|
|
|
witnesses
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn calculate_tree_state_v2(cbs: &[CompactBlock], blocks: &[DecryptedBlock]) -> Vec<Witness> {
|
|
|
|
let mut p = 0usize;
|
|
|
|
let mut nodes: Vec<Node> = vec![];
|
|
|
|
let mut positions: Vec<usize> = vec![];
|
|
|
|
|
|
|
|
let start = Instant::now();
|
|
|
|
for (cb, block) in cbs.iter().zip(blocks) {
|
|
|
|
assert_eq!(cb.height as u32, block.height);
|
2021-06-21 17:33:13 -07:00
|
|
|
if !block.notes.is_empty() {
|
|
|
|
println!("{} {}", block.height, block.notes.len());
|
|
|
|
}
|
2021-06-18 01:17:41 -07:00
|
|
|
let mut notes = block.notes.iter();
|
|
|
|
let mut n = notes.next();
|
2021-06-21 17:33:13 -07:00
|
|
|
let mut i = 0usize;
|
2021-06-18 01:17:41 -07:00
|
|
|
for tx in cb.vtx.iter() {
|
|
|
|
for co in tx.outputs.iter() {
|
|
|
|
let mut cmu = [0u8; 32];
|
|
|
|
cmu.copy_from_slice(&co.cmu);
|
|
|
|
let node = Node::new(cmu);
|
|
|
|
nodes.push(node);
|
|
|
|
|
|
|
|
if let Some(nn) = n {
|
|
|
|
if i == nn.position {
|
|
|
|
positions.push(p);
|
|
|
|
n = notes.next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i += 1;
|
|
|
|
p += 1;
|
|
|
|
}
|
|
|
|
}
|
2021-06-17 09:56:20 -07:00
|
|
|
}
|
2021-06-18 01:17:41 -07:00
|
|
|
info!("Build CMU list: {} ms - {} nodes", start.elapsed().as_millis(), nodes.len());
|
|
|
|
|
2021-06-21 17:33:13 -07:00
|
|
|
let witnesses: Vec<_> = positions.iter().map(|p| Witness::new(*p, 0, None)).collect();
|
|
|
|
let (_, new_witnesses) = advance_tree(CTree::new(), &witnesses, &mut nodes);
|
2021-06-18 01:17:41 -07:00
|
|
|
info!("Tree State & Witnesses: {} ms", start.elapsed().as_millis());
|
2021-06-21 17:33:13 -07:00
|
|
|
new_witnesses
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn connect_lightwalletd() -> anyhow::Result<CompactTxStreamerClient<Channel>> {
|
|
|
|
let mut channel = tonic::transport::Channel::from_shared(LWD_URL)?;
|
|
|
|
if LWD_URL.starts_with("https") {
|
|
|
|
let pem = include_bytes!("ca.pem");
|
|
|
|
let ca = Certificate::from_pem(pem);
|
|
|
|
let tls = ClientTlsConfig::new().ca_certificate(ca);
|
|
|
|
channel = channel.tls_config(tls)?;
|
|
|
|
}
|
|
|
|
let client = CompactTxStreamerClient::connect(channel).await?;
|
|
|
|
Ok(client)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn sync(ivk: &str) -> anyhow::Result<()> {
|
|
|
|
let fvk =
|
|
|
|
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &ivk)
|
|
|
|
.unwrap()
|
|
|
|
.unwrap();
|
|
|
|
let decrypter = DecryptNode::new(vec![fvk]);
|
|
|
|
let mut client = connect_lightwalletd().await?;
|
|
|
|
let start_height: u32 = crate::NETWORK
|
|
|
|
.activation_height(NetworkUpgrade::Sapling)
|
|
|
|
.unwrap()
|
|
|
|
.into();
|
|
|
|
let end_height = get_latest_height(&mut client).await?;
|
|
|
|
|
|
|
|
let start = Instant::now();
|
|
|
|
let cbs = download_chain(&mut client, start_height, end_height).await?;
|
|
|
|
eprintln!("Download chain: {} ms", start.elapsed().as_millis());
|
|
|
|
|
|
|
|
let start = Instant::now();
|
|
|
|
let blocks = decrypter.decrypt_blocks(&cbs);
|
|
|
|
eprintln!("Decrypt Notes: {} ms", start.elapsed().as_millis());
|
|
|
|
|
|
|
|
let start = Instant::now();
|
|
|
|
let witnesses = calculate_tree_state_v2(&cbs, &blocks);
|
|
|
|
eprintln!("Tree State & Witnesses: {} ms", start.elapsed().as_millis());
|
|
|
|
|
|
|
|
eprintln!("# Witnesses {}", witnesses.len());
|
|
|
|
for w in witnesses.iter() {
|
|
|
|
let mut bb: Vec<u8> = vec![];
|
|
|
|
w.write(&mut bb).unwrap();
|
|
|
|
log::info!("{}", hex::encode(&bb));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2021-06-17 09:56:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2021-06-18 01:17:41 -07:00
|
|
|
#[allow(unused_imports)]
|
|
|
|
use crate::chain::{download_chain, get_latest_height, get_tree_state, calculate_tree_state_v1, calculate_tree_state_v2, DecryptNode};
|
2021-06-17 09:56:20 -07:00
|
|
|
use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
|
|
|
|
use crate::NETWORK;
|
|
|
|
use dotenv;
|
|
|
|
use std::time::Instant;
|
2021-06-18 01:17:41 -07:00
|
|
|
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
|
|
|
use zcash_primitives::consensus::{NetworkUpgrade, Parameters};
|
|
|
|
use crate::chain::LWD_URL;
|
2021-06-17 09:56:20 -07:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_get_latest_height() -> anyhow::Result<()> {
|
2021-06-18 01:17:41 -07:00
|
|
|
let mut client = CompactTxStreamerClient::connect(LWD_URL).await?;
|
2021-06-17 09:56:20 -07:00
|
|
|
let height = get_latest_height(&mut client).await?;
|
|
|
|
assert!(height > 1288000);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_download_chain() -> anyhow::Result<()> {
|
|
|
|
dotenv::dotenv().unwrap();
|
2021-06-21 17:33:13 -07:00
|
|
|
let fvk = dotenv::var("FVK").unwrap();
|
2021-06-17 09:56:20 -07:00
|
|
|
|
2021-06-18 01:17:41 -07:00
|
|
|
let fvk =
|
2021-06-21 17:33:13 -07:00
|
|
|
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk)
|
2021-06-18 01:17:41 -07:00
|
|
|
.unwrap()
|
|
|
|
.unwrap();
|
2021-06-21 17:33:13 -07:00
|
|
|
let decrypter = DecryptNode::new(vec![fvk]);
|
2021-06-18 01:17:41 -07:00
|
|
|
let mut client = CompactTxStreamerClient::connect(LWD_URL).await?;
|
|
|
|
let start_height: u32 = crate::NETWORK
|
|
|
|
.activation_height(NetworkUpgrade::Sapling)
|
|
|
|
.unwrap()
|
|
|
|
.into();
|
2021-06-17 09:56:20 -07:00
|
|
|
let end_height = get_latest_height(&mut client).await?;
|
|
|
|
|
|
|
|
let start = Instant::now();
|
|
|
|
let cbs = download_chain(&mut client, start_height, end_height).await?;
|
|
|
|
eprintln!("Download chain: {} ms", start.elapsed().as_millis());
|
|
|
|
|
|
|
|
let start = Instant::now();
|
2021-06-18 01:17:41 -07:00
|
|
|
let blocks = decrypter.decrypt_blocks(&cbs);
|
2021-06-17 09:56:20 -07:00
|
|
|
eprintln!("Decrypt Notes: {} ms", start.elapsed().as_millis());
|
|
|
|
|
2021-06-18 01:17:41 -07:00
|
|
|
// no need to calculate tree before the first note if we can
|
|
|
|
// get it from the server
|
|
|
|
// disabled because I want to see the performance of a complete scan
|
|
|
|
|
|
|
|
// let first_block = blocks.iter().find(|b| !b.notes.is_empty()).unwrap();
|
|
|
|
// let height = first_block.height - 1;
|
|
|
|
// let tree_state = get_tree_state(&mut client, height).await;
|
|
|
|
// let tree_state = hex::decode(tree_state).unwrap();
|
|
|
|
// let tree_state = CommitmentTree::<Node>::read(&*tree_state).unwrap();
|
|
|
|
|
|
|
|
// let witnesses = calculate_tree_state(&cbs, &blocks, 0, tree_state);
|
|
|
|
|
2021-06-21 17:33:13 -07:00
|
|
|
let start = Instant::now();
|
2021-06-18 01:17:41 -07:00
|
|
|
let witnesses = calculate_tree_state_v2(&cbs, &blocks);
|
2021-06-21 17:33:13 -07:00
|
|
|
eprintln!("Tree State & Witnesses: {} ms", start.elapsed().as_millis());
|
2021-06-18 01:17:41 -07:00
|
|
|
|
|
|
|
eprintln!("# Witnesses {}", witnesses.len());
|
|
|
|
for w in witnesses.iter() {
|
|
|
|
let mut bb: Vec<u8> = vec![];
|
|
|
|
w.write(&mut bb).unwrap();
|
2021-06-21 17:33:13 -07:00
|
|
|
log::info!("{}", hex::encode(&bb));
|
2021-06-18 01:17:41 -07:00
|
|
|
}
|
|
|
|
|
2021-06-17 09:56:20 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|