From ff51a50bfdfc833c2dcf509a27660a6bb98f5306 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Thu, 12 Sep 2019 13:11:18 -0700 Subject: [PATCH] Fetch and add UTXOs to wallet --- rust-lightclient/src/lightclient.rs | 59 ++++++++++++++++++++++------- rust-lightclient/src/lightwallet.rs | 58 ++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 16 deletions(-) diff --git a/rust-lightclient/src/lightclient.rs b/rust-lightclient/src/lightclient.rs index bd14291..42022da 100644 --- a/rust-lightclient/src/lightclient.rs +++ b/rust-lightclient/src/lightclient.rs @@ -26,7 +26,7 @@ use tower_hyper::{client, util}; use tower_util::MakeService; use futures::stream::Stream; -use crate::grpc_client::{ChainSpec, BlockId, BlockRange, RawTransaction, TransparentAddress, Utxo, TxFilter, Empty}; +use crate::grpc_client::{ChainSpec, BlockId, BlockRange, RawTransaction, TransparentAddress, TxFilter, Empty}; use crate::grpc_client::client::CompactTxStreamer; // Used below to return the grpc "Client" type to calling methods @@ -83,8 +83,9 @@ impl LightClient { self.wallet.last_scanned_height() as u64 } - pub fn do_address(&self) -> json::JsonValue{ - let addresses = self.wallet.address.iter().map( |ad| { + pub fn do_address(&self) -> json::JsonValue { + // Collect z addresses + let z_addresses = self.wallet.address.iter().map( |ad| { let address = encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &ad); object!{ "address" => address.clone(), @@ -93,10 +94,18 @@ impl LightClient { } }).collect::>(); + // Collect t addresses + let t_addresses = self.wallet.tkeys.iter().map( |pk| { + object!{ + "address" => LightWallet::address_from_pk(&pk), + } + }).collect::>(); + object!{ - "balance" => self.wallet.balance(None), - "verified_balance" => self.wallet.verified_balance(None), - "addresses" => addresses + "balance" => self.wallet.balance(None), + "verified_balance" => self.wallet.verified_balance(None), + "z_addresses" => z_addresses, + "t_addresses" => t_addresses, } } @@ -142,6 +151,7 @@ impl LightClient { let mut spent_notes : Vec = vec![]; let mut pending_notes: Vec = vec![]; + // Collect Sapling notes self.wallet.txs.read().unwrap().iter() .flat_map( |(txid, wtx)| { wtx.notes.iter().map(move |nd| @@ -166,10 +176,28 @@ impl LightClient { } }); + // Collect UTXOs + let utxos = self.wallet.txs.read().unwrap().iter() + .flat_map( |(_, wtx)| { + wtx.utxos.iter().map(move |utxo| { + object!{ + "created_in_block" => wtx.block, + "created_in_txid" => format!("{}", utxo.txid), + "value" => utxo.value, + "is_change" => false, // TODO: Identify notes as change + "address" => utxo.address.clone(), + "spent" => utxo.spent.map(|spent_txid| format!("{}", spent_txid)), + "unconfirmed_spent" => utxo.unconfirmed_spent.map(|spent_txid| format!("{}", spent_txid)), + } + }) + }) + .collect::>(); + object!{ "spent_notes" => spent_notes, "unspent_notes" => unspent_notes, - "pending_notes" => pending_notes + "pending_notes" => pending_notes, + "utxos" => utxos, } } @@ -282,11 +310,14 @@ impl LightClient { last_block, bytes_downloaded.load(Ordering::SeqCst) / 1024); // Fetch UTXOs - self.fetch_utxos("tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ".to_string(), |utxo| { - println!("Got txid {}", utxo.txid); - println!("Got script {}", hex::encode(utxo.script)); - }); - + self.wallet.tkeys.iter() + .map( |pk| LightWallet::address_from_pk(&pk)) + .for_each( |taddr| { + let wallet = self.wallet.clone(); + self.fetch_utxos(taddr, move |utxo| { + wallet.scan_utxo(&utxo); + }); + }); // Get the Raw transaction for all the wallet transactions @@ -441,8 +472,10 @@ impl LightClient { txid: TxId {0: txid_bytes}, output_index: b.output_index, script: b.script.to_vec(), - height: b.height, + height: b.height as i32, value: b.value, + spent: None, + unconfirmed_spent: None, }; c(u); diff --git a/rust-lightclient/src/lightwallet.rs b/rust-lightclient/src/lightwallet.rs index 1b1fb2d..6cad9cb 100644 --- a/rust-lightclient/src/lightwallet.rs +++ b/rust-lightclient/src/lightwallet.rs @@ -13,7 +13,8 @@ use pairing::bls12_381::{Bls12}; use ff::{PrimeField, PrimeFieldRepr}; use zcash_client_backend::{ - constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS, encoding::encode_payment_address, + constants::testnet::{HRP_SAPLING_PAYMENT_ADDRESS,B58_PUBKEY_ADDRESS_PREFIX,}, + encoding::encode_payment_address, proto::compact_formats::CompactBlock, welding_rig::scan_block, }; @@ -324,13 +325,17 @@ impl SaplingNoteData { } } +#[derive(Clone, Debug)] pub struct Utxo { pub address: String, pub txid: TxId, pub output_index: u64, pub script: Vec, pub value: u64, - pub height: u64 + pub height: i32, + + pub spent: Option, // If this utxo was confirmed spent + pub unconfirmed_spent: Option, // If this utxo was spent in a send, but has not yet been confirmed. } pub struct WalletTx { @@ -340,9 +345,12 @@ pub struct WalletTx { // WalletTx in LightWallet::txs) pub txid: TxId, - // List of all notes recieved in this tx. Note that some of these might be change notes. + // List of all notes recieved in this tx. Some of these might be change notes. pub notes: Vec, + // List of all Utxos recieved in this Tx. Some of these might be change notes + pub utxos: Vec, + // Total shielded value spent in this Tx. Note that this is the value of notes spent, // the change is returned in the notes above. Subtract the two to get the actual value spent. // Also note that even after subtraction, you might need to account for transparent inputs and outputs @@ -374,6 +382,7 @@ impl WalletTx { block, txid, notes, + utxos: vec![], total_shielded_value_spent }) } @@ -430,6 +439,9 @@ pub struct LightWallet { extfvks: Vec, pub address: Vec>, + // Transparent keys. TODO: Make it not pub + pub tkeys: Vec, + blocks: Arc>>, pub txs: Arc>>, } @@ -469,6 +481,9 @@ impl LightWallet { Language::English).unwrap().entropy()); } + // TODO: Generate transparent addresses from the seed + let tpk = secp256k1::SecretKey::from_slice(&[1u8; 32]).unwrap(); + // Derive only the first address // TODO: We need to monitor addresses, and always keep 1 "free" address, so // users can import a seed phrase and automatically get all used addresses @@ -479,6 +494,7 @@ impl LightWallet { extsks: vec![extsk], extfvks: vec![extfvk], address: vec![address], + tkeys: vec![tpk], blocks: Arc::new(RwLock::new(vec![])), txs: Arc::new(RwLock::new(HashMap::new())), }) @@ -503,6 +519,9 @@ impl LightWallet { let addresses = extfvks.iter().map( |fvk| fvk.default_address().unwrap().1 ) .collect::>>(); + // TODO: Generate transparent addresses from the seed + let tpk = secp256k1::SecretKey::from_slice(&[1u8; 32]).unwrap(); + let blocks = Vector::read(&mut reader, |r| BlockData::read(r))?; let txs_tuples = Vector::read(&mut reader, |r| { @@ -518,6 +537,7 @@ impl LightWallet { extsks: extsks, extfvks: extfvks, address: addresses, + tkeys: vec![tpk], blocks: Arc::new(RwLock::new(blocks)), txs: Arc::new(RwLock::new(txs)) }) @@ -618,6 +638,15 @@ impl LightWallet { &extfvk.fvk.vk.into_payment_address(diversifier, &JUBJUB).unwrap()) } + pub fn address_from_pk(pk: &secp256k1::SecretKey) -> String { + // Encode into t address + let mut hash160 = ripemd160::Ripemd160::new(); + hash160.input(Sha256::digest(&pk[..].to_vec())); + + // TODO: The taddr version prefix needs to be different for testnet and mainnet + hash160.result().to_base58check(&B58_PUBKEY_ADDRESS_PREFIX, &[]) + } + pub fn get_seed_phrase(&self) -> String { Mnemonic::from_entropy(&self.seed, Language::English, @@ -681,6 +710,7 @@ impl LightWallet { .sum::() } + // Scan the full Tx and update memos for incoming shielded transactions pub fn scan_full_tx(&self, tx: &Transaction) { for output in tx.shielded_outputs.iter() { @@ -710,6 +740,27 @@ impl LightWallet { } } + pub fn scan_utxo(&self, utxo: &Utxo) { + // Grab a write lock to the wallet so we can update it. + let mut txs = self.txs.write().unwrap(); + + // If the Txid doesn't exist, create it + if !txs.contains_key(&utxo.txid) { + let tx_entry = WalletTx { + block: utxo.height, + txid: utxo.txid, + notes: vec![], + utxos: vec![], + total_shielded_value_spent: 0 + }; + txs.insert(utxo.txid, tx_entry); + } + let tx_entry = txs.get_mut(&utxo.txid).unwrap(); + + // Add the utxo into the Wallet Tx entry + tx_entry.utxos.push(utxo.clone()); + } + pub fn scan_block(&self, block: &[u8]) -> bool { let block: CompactBlock = match parse_from_bytes(block) { Ok(block) => block, @@ -837,6 +888,7 @@ impl LightWallet { block: block_data.height, txid: tx.txid, notes: vec![], + utxos: vec![], total_shielded_value_spent: 0 }; txs.insert(tx.txid, tx_entry);