From c2f2cf207e258266a2e01a1aff458df85dffb064 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Mon, 23 Sep 2019 14:06:16 -0700 Subject: [PATCH] Send to address test --- src/lightwallet/mod.rs | 163 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 2 deletions(-) diff --git a/src/lightwallet/mod.rs b/src/lightwallet/mod.rs index 0f9825f..bfd7b24 100644 --- a/src/lightwallet/mod.rs +++ b/src/lightwallet/mod.rs @@ -3,6 +3,8 @@ use std::io::{self, Read, Write}; use std::cmp; use std::collections::{HashMap, HashSet}; use std::sync::{Arc, RwLock}; +use std::fs::File; +use std::io::{BufReader, BufWriter, Error, ErrorKind}; use log::{info, warn, error}; @@ -131,6 +133,27 @@ impl LightWallet { return 1; } + fn get_sapling_params(config: &LightClientConfig) -> Result<(Vec, Vec), Error> { + // Read Sapling Params + let mut sapling_output = vec![]; + let mut f = match File::open(config.get_params_path("sapling-output.params")) { + Ok(file) => file, + Err(_) => return Err(Error::new(ErrorKind::NotFound, + format!("Couldn't read {}", config.get_params_path("sapling-output.params").display()))) + }; + f.read_to_end(&mut sapling_output)?; + + let mut sapling_spend = vec![]; + let mut f = match File::open(config.get_params_path("sapling-spend.params")) { + Ok(file) => file, + Err(_) => return Err(Error::new(ErrorKind::NotFound, + format!("Couldn't read {}", config.get_params_path("sapling-spend.params").display()))) + }; + f.read_to_end(&mut sapling_spend)?; + + Ok((sapling_spend, sapling_output)) + } + fn get_pk_from_bip39seed(bip39seed: &[u8]) -> (ExtendedSpendingKey, ExtendedFullViewingKey, PaymentAddress) { let extsk: ExtendedSpendingKey = ExtendedSpendingKey::from_path( @@ -663,7 +686,6 @@ impl LightWallet { }, None => {} }; - } } } @@ -1087,7 +1109,7 @@ pub mod tests { use ff::{Field, PrimeField, PrimeFieldRepr}; use pairing::bls12_381::Bls12; use rand_core::{RngCore, OsRng}; - use protobuf::Message; + use protobuf::{Message, UnknownFields, CachedSize, RepeatedField}; use zcash_client_backend::{encoding::encode_payment_address, proto::compact_formats::{ CompactBlock, CompactOutput, CompactSpend, CompactTx, @@ -1102,6 +1124,7 @@ pub mod tests { transaction::{ TxId, Transaction, TransactionData, components::{TxOut, TxIn, OutPoint, Amount,}, + components::amount::DEFAULT_FEE, }, zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, JUBJUB, @@ -1141,6 +1164,47 @@ pub mod tests { BlockHash(self.block.hash[..].try_into().unwrap()) } + fn tx_to_compact_tx(tx: &Transaction, index: u64) -> CompactTx { + let spends = tx.shielded_spends.iter().map(|s| { + let mut c_spend = CompactSpend::default(); + c_spend.set_nf(s.nullifier.to_vec()); + + c_spend + }).collect::>(); + + let outputs = tx.shielded_outputs.iter().map(|o| { + let mut c_out = CompactOutput::default(); + + let mut cmu_bytes = vec![]; + o.cmu.into_repr().write_le(&mut cmu_bytes).unwrap(); + + let mut epk_bytes = vec![]; + o.ephemeral_key.write(&mut epk_bytes).unwrap(); + + c_out.set_cmu(cmu_bytes); + c_out.set_epk(epk_bytes); + c_out.set_ciphertext(o.enc_ciphertext[0..52].to_vec()); + + c_out + }).collect::>(); + + CompactTx { + index, + hash: tx.txid().0.to_vec(), + fee: 0, // TODO: Get Fee + spends: RepeatedField::from_vec(spends), + outputs: RepeatedField::from_vec(outputs), + unknown_fields: UnknownFields::default(), + cached_size: CachedSize::default(), + } + } + + // Convert the transaction into a CompactTx and add it to this block + fn add_tx(&mut self, tx: &Transaction) { + let ctx = FakeCompactBlock::tx_to_compact_tx(&tx, self.block.vtx.len() as u64); + self.block.vtx.push(ctx); + } + // Add a new tx into the block, paying the given address the amount. // Returns the nullifier of the new note. fn add_tx_paying(&mut self, extfvk: ExtendedFullViewingKey, value: u64) @@ -1663,4 +1727,99 @@ pub mod tests { assert_eq!(txs[&txid2].total_transparent_value_spent, TAMOUNT1); } } + + #[test] + fn test_z_spend() { + let config = LightClientConfig { + server: "0.0.0.0:0".to_string(), + chain_name: "test".to_string(), + sapling_activation_height: 0 + }; + + let wallet = LightWallet::new(None, &config).unwrap(); + + const AMOUNT1:u64 = 50000; + // Address is encoded in bech32 + let address = Some(encode_payment_address(wallet.config.hrp_sapling_address(), + &wallet.extfvks[0].default_address().unwrap().1)); + + let mut cb1 = FakeCompactBlock::new(0, BlockHash([0; 32])); + let (_, txid1) = cb1.add_tx_paying(wallet.extfvks[0].clone(), AMOUNT1); + wallet.scan_block(&cb1.as_bytes()).unwrap(); + + // We have one note + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1); + assert_eq!(txs[&txid1].notes[0].spent, None); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, None); + } + + assert_eq!(wallet.verified_zbalance(None), AMOUNT1); + + // Create a new block so that the note is now verified to be spent + let cb2 = FakeCompactBlock::new(1, cb1.hash()); + wallet.scan_block(&cb2.as_bytes()).unwrap(); + + let fvk = ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[1u8; 32])); + let ext_address = encode_payment_address(wallet.config.hrp_sapling_address(), + &fvk.default_address().unwrap().1); + + const AMOUNT_SENT: u64 = 20; + + let outgoing_memo = "Outgoing Memo".to_string(); + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap(); + let (ss, so) = LightWallet::get_sapling_params(&config).unwrap(); + + // Create a tx and send to address + let raw_tx = wallet.send_to_address(branch_id, &ss, &so, + &ext_address, AMOUNT_SENT, Some(outgoing_memo.clone())).unwrap(); + + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + // Now, the note should be unconfirmed spent + { + let txs = wallet.txs.read().unwrap(); + + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1); + assert_eq!(txs[&txid1].notes[0].spent, None); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, Some(sent_txid)); + } + + let mut cb3 = FakeCompactBlock::new(2, cb2.hash()); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + + // Now this new Spent tx should be in, so the note should be marked confirmed spent + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1); + assert_eq!(txs[&txid1].notes[0].spent, Some(sent_txid)); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, None); + + // The sent tx should generate change + assert_eq!(txs[&sent_txid].notes.len(), 1); + assert_eq!(txs[&sent_txid].notes[0].note.value, AMOUNT1 - AMOUNT_SENT - fee); + assert_eq!(txs[&sent_txid].notes[0].spent, None); + assert_eq!(txs[&sent_txid].notes[0].unconfirmed_spent, None); + } + + // Now, full scan the Tx, which should populate the Outgoing Meta data + wallet.scan_full_tx(&sent_tx, 2); + + // Check Outgoing Metadata + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&sent_txid].outgoing_metadata.len(), 1); + + assert_eq!(txs[&sent_txid].outgoing_metadata[0].address, ext_address); + assert_eq!(txs[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT); + assert_eq!(txs[&sent_txid].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo); + } + } } \ No newline at end of file