diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index 8962f19..c7fcf95 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -870,78 +870,112 @@ impl LightClient { .flat_map(| (_k, v) | { let mut txns: Vec = vec![]; - if v.total_shielded_value_spent + v.total_transparent_value_spent > 0 { - // If money was spent, create a transaction. For this, we'll subtract - // all the change notes. TODO: Add transparent change here to subtract it also - let total_change: u64 = v.notes.iter() - .filter( |nd| nd.is_change ) - .map( |nd| nd.note.value ) - .sum(); + //Get totals from outgoing metadata + let total_change: u64 = v.outgoing_metadata_change.iter().map(|u| u.value).sum::(); + let total_send: u64 = v.outgoing_metadata.iter().map(|u| u.value).sum::(); - // TODO: What happens if change is > than sent ? + //Get Change address from outgoing change metadata + let change_addresses = v.outgoing_metadata_change.iter() + .map(|om| om.address.clone()) + .collect::>(); - // Collect outgoing metadata - let outgoing_json = v.outgoing_metadata.iter() - .map(|om| - object!{ - "address" => om.address.clone(), - "value" => om.value, - "memo" => LightWallet::memo_str(&Some(om.memo.clone())), - }) - .collect::>(); - - txns.push(object! { - "block_height" => v.block, - "datetime" => v.datetime, - "txid" => format!("{}", v.txid), - "amount" => total_change as i64 - - v.total_shielded_value_spent as i64 - - v.total_transparent_value_spent as i64, - "outgoing_metadata" => outgoing_json, - }); - } - - // For each sapling note that is not a change, add a Tx. - txns.extend(v.notes.iter() + // Collect incoming metadata + let mut incoming_json = v.notes.iter() .filter( |nd| !nd.is_change ) .enumerate() - .map ( |(i, nd)| + .map ( |(_i, nd)| object! { - "block_height" => v.block, - "datetime" => v.datetime, - "position" => i, - "txid" => format!("{}", v.txid), - "amount" => nd.note.value as i64, "address" => LightWallet::note_address(self.config.hrp_sapling_address(), nd), + "value" => nd.note.value as i64, "memo" => LightWallet::memo_str(&nd.memo), }) - ); + .collect::>(); - // Get the total transparent received - let total_transparent_received = v.utxos.iter().map(|u| u.value).sum::(); - if total_transparent_received > v.total_transparent_value_spent { - // Create an input transaction for the transparent value as well. - txns.push(object!{ - "block_height" => v.block, - "datetime" => v.datetime, - "txid" => format!("{}", v.txid), - "amount" => total_transparent_received as i64 - v.total_transparent_value_spent as i64, - "address" => v.utxos.iter().map(|u| u.address.clone()).collect::>().join(","), - "memo" => None:: - }) + let incoming_t_json = v.utxos.iter() + .filter(|u| !change_addresses.contains(&u.address)) + .map( |uo| + object! { + "address" => uo.address.clone(), + "value" => uo.value.clone() as i64, + "memo" => None::, + }) + .collect::>(); + + for json in incoming_t_json { + incoming_json.push(json.clone()); } - txns - }) - .collect::>(); + // Collect incoming metadata change + let mut incoming_change_json = v.notes.iter() + .filter( |nd| nd.is_change ) + .enumerate() + .map ( |(_i, nd)| + object! { + "address" => LightWallet::note_address(self.config.hrp_sapling_address(), nd), + "value" => nd.note.value as i64, + "memo" => LightWallet::memo_str(&nd.memo), + }) + .collect::>(); + + let incoming_t_change_json = v.utxos.iter() + .filter(|u| change_addresses.contains(&u.address)) + .map( |uo| + object! { + "address" => uo.address.clone(), + "value" => uo.value.clone() as i64, + "memo" => None::, + }) + .collect::>(); + + for json in incoming_t_change_json { + incoming_change_json.push(json.clone()); + } + + // Collect outgoing metadata + let outgoing_json = v.outgoing_metadata.iter() + .map(|om| + object!{ + "address" => om.address.clone(), + "value" => om.value, + "memo" => LightWallet::memo_str(&Some(om.memo.clone())), + }) + .collect::>(); + + // Collect outgoing metadata change + let outgoing_change_json = v.outgoing_metadata_change.iter() + .map(|om| + object!{ + "address" => om.address.clone(), + "value" => om.value, + "memo" => LightWallet::memo_str(&Some(om.memo.clone())), + }) + .collect::>(); + + txns.push(object! { + "block_height" => v.block, + "datetime" => v.datetime, + "txid" => format!("{}", v.txid), + "amount" => total_change as i64 + - v.total_shielded_value_spent as i64 + - v.total_transparent_value_spent as i64, + "fee" => v.total_shielded_value_spent as i64 + + v.total_transparent_value_spent as i64 + - total_change as i64 + - total_send as i64, + "incoming_metadata" => incoming_json, + "incoming_metadata_change" => incoming_change_json, + "outgoing_metadata" => outgoing_json, + "outgoing_metadata_change" => outgoing_change_json, + + }); + txns + }) + .collect::>(); // Add in all mempool txns tx_list.extend(wallet.mempool_txs.read().unwrap().iter().map( |(_, wtx)| { - use zcash_primitives::transaction::components::amount::DEFAULT_FEE; - use std::convert::TryInto; - let amount: u64 = wtx.outgoing_metadata.iter().map(|om| om.value).sum::(); - let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + let fee: u64 = wtx.total_shielded_value_spent - amount; // Collect outgoing metadata let outgoing_json = wtx.outgoing_metadata.iter() @@ -957,6 +991,7 @@ impl LightClient { "datetime" => wtx.datetime, "txid" => format!("{}", wtx.txid), "amount" => -1 * (fee + amount) as i64, + "fee" => fee as i64, "unconfirmed" => true, "outgoing_metadata" => outgoing_json, } @@ -1351,6 +1386,7 @@ impl LightClient { info!("Transaction Complete"); + match rawtx { Ok(txbytes) => broadcast_raw_tx(&self.get_server_uri(), txbytes), Err(e) => Err(format!("Error: No Tx to broadcast. Error was: {}", e)) diff --git a/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index a39a8e1..7dee8f6 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -971,13 +971,24 @@ impl LightWallet { pub fn scan_full_tx(&self, tx: &Transaction, height: i32, datetime: u64) { let mut total_transparent_spend: u64 = 0; + //Get Value Balance + { + let mut txs = self.txs.write().unwrap(); + match txs.get_mut(&tx.txid()) { + Some(wtx) => wtx.value_balance = tx.value_balance.into(), + None => {}, + }; + } + + // Scan all the inputs to see if we spent any transparent funds in this tx - for vin in tx.vin.iter() { + let mut tinputs = Vec::new(); + + for vin in tx.vin.iter() { // Find the txid in the list of utxos that we have. let txid = TxId {0: vin.prevout.hash}; match self.txs.write().unwrap().get_mut(&txid) { Some(wtx) => { - //println!("Looking for {}, {}", txid, vin.prevout.n); // One of the tx outputs is a match let spent_utxo = wtx.utxos.iter_mut() @@ -988,6 +999,7 @@ impl LightWallet { info!("Spent utxo from {} was spent in {}", txid, tx.txid()); su.spent = Some(tx.txid().clone()); su.unconfirmed_spent = None; + tinputs.push(su.address.clone()); total_transparent_spend += su.value; }, @@ -1032,42 +1044,26 @@ impl LightWallet { } } + + let mut outgoing = Vec::new(); + let mut outgoing_change = Vec::new(); { let total_shielded_value_spent = self.txs.read().unwrap().get(&tx.txid()).map_or(0, |wtx| wtx.total_shielded_value_spent); if total_transparent_spend + total_shielded_value_spent > 0 { // We spent money in this Tx, so grab all the transparent outputs (except ours) and add them to the // outgoing metadata - // Collect our t-addresses - let wallet_taddrs = self.taddresses.read().unwrap().iter() - .map(|a| a.clone()) - .collect::>(); - for vout in tx.vout.iter() { let taddr = self.address_from_pubkeyhash(vout.script_pubkey.address()); - //if taddr.is_some() && !wallet_taddrs.contains(&taddr.clone().unwrap()) { if taddr.is_some() { let taddr = taddr.unwrap(); - // Add it to outgoing metadata - let mut txs = self.txs.write().unwrap(); - if txs.get(&tx.txid()).unwrap().outgoing_metadata.iter() - .find(|om| - om.address == taddr && Amount::from_u64(om.value).unwrap() == vout.value) - .is_some() { - warn!("Duplicate outgoing metadata"); - continue; + if tinputs.contains(&taddr) { + outgoing_change.push((taddr, vout.value.into(), Memo::default())); + } else { + outgoing.push((taddr, vout.value.into(), Memo::default())); } - - // Write the outgoing metadata - txs.get_mut(&tx.txid()).unwrap() - .outgoing_metadata - .push(OutgoingTxMetadata{ - address: taddr, - value: vout.value.into(), - memo: Memo::default(), - }); } } } @@ -1115,10 +1111,49 @@ impl LightWallet { // the memo and value for our records // First, collect all our z addresses, to check for change - // Collect z addresses - let z_addresses = self.zaddress.read().unwrap().iter().map( |ad| { - encode_payment_address(self.config.hrp_sapling_address(), &ad) - }).collect::>(); + // Collect z addresses spent from + + let mut zinputs = Vec::new(); + { + + let mut txs = self.txs.write().unwrap(); + let nfs: Vec<_>; + + nfs = txs + .iter() + .map(|(txid, tx)| { + let txid = *txid; + tx.notes.iter().filter_map(move |nd| { + Some((nd.nullifier, nd.account, txid)) + }) + }) + .flatten() + .collect(); + + for spend in &tx.shielded_spends { + let txid = nfs + .iter() + .find(|(nf, _, _)| &nf[..] == &spend.nullifier[..]) + .unwrap() + .2; + let spent_note = txs + .get_mut(&txid) + .unwrap() + .notes + .iter_mut() + .find(|nd| &nd.nullifier[..] == &spend.nullifier[..]) + .unwrap(); + + zinputs.push(encode_payment_address( + self.config.hrp_sapling_address(), + &spent_note.extfvk.fvk.vk + .to_payment_address(spent_note.diversifier, &JUBJUB).unwrap())); + + } + + } + + // Search all ovks that we have let ovks: Vec<_> = self.extfvks.read().unwrap().iter().map( @@ -1137,34 +1172,57 @@ impl LightWallet { &payment_address); // Check if this is a change address - // if z_addresses.contains(&address) { - // continue; - // } - - // Update the WalletTx - // Do it in a short scope because of the write lock. - { - info!("A sapling output was sent in {}", tx.txid()); - - let mut txs = self.txs.write().unwrap(); - if txs.get(&tx.txid()).unwrap().outgoing_metadata.iter() - .find(|om| om.address == address && om.value == note.value && om.memo == memo) - .is_some() { - warn!("Duplicate outgoing metadata"); - continue; - } - - // Write the outgoing metadata - txs.get_mut(&tx.txid()).unwrap() - .outgoing_metadata - .push(OutgoingTxMetadata{ - address, value: note.value, memo, - }); + if zinputs.contains(&address) { + outgoing_change.push((address, note.value, memo)); + } else { + outgoing.push((address, note.value, memo)); } }, None => {} }; } + + + { + // Update the WalletTx + // Do it in a short scope because of the write lock. + // Write the outgoing metadata + for metadata in outgoing.iter().enumerate() { + + let mut txs = self.txs.write().unwrap(); + if txs.get(&tx.txid()).unwrap().outgoing_metadata.iter() + .find(|om| om.address == (metadata.1).0 && om.value == (metadata.1).1 && om.memo == (metadata.1).2) + .is_some() { + warn!("Duplicate outgoing metadata"); + continue; + } + + txs.get_mut(&tx.txid()).unwrap() + .outgoing_metadata + .push(OutgoingTxMetadata{ + address: (metadata.1).0.clone(), + value: (metadata.1).1.clone(), + memo: (metadata.1).2.clone()}); + } + + for metadata in outgoing_change.iter().enumerate() { + + let mut txs = self.txs.write().unwrap(); + if txs.get(&tx.txid()).unwrap().outgoing_metadata_change.iter() + .find(|om| om.address == (metadata.1).0 && om.value == (metadata.1).1 && om.memo == (metadata.1).2) + .is_some() { + warn!("Duplicate outgoing metadata change"); + continue; + } + + txs.get_mut(&tx.txid()).unwrap() + .outgoing_metadata_change + .push(OutgoingTxMetadata{ + address: (metadata.1).0.clone(), + value: (metadata.1).1.clone(), + memo: (metadata.1).2.clone()}); + } + } } // Mark this Tx as scanned @@ -1912,6 +1970,8 @@ impl LightWallet { println!("{}: Transaction created", now() - start_time); println!("Transaction ID: {}", tx.txid()); + + // Mark notes as spent. { // Mark sapling notes as unconfirmed spent @@ -1961,6 +2021,7 @@ impl LightWallet { // Create a new WalletTx let mut wtx = WalletTx::new(height as i32, now() as u64, &tx.txid()); wtx.outgoing_metadata = outgoing_metadata; + wtx.total_shielded_value_spent = total_value + fee; // Add it into the mempool mempool_txs.insert(tx.txid(), wtx); diff --git a/lib/src/lightwallet/data.rs b/lib/src/lightwallet/data.rs index 89f9980..d6877f4 100644 --- a/lib/src/lightwallet/data.rs +++ b/lib/src/lightwallet/data.rs @@ -387,13 +387,19 @@ pub struct WalletTx { // All outgoing sapling sends to addresses outside this wallet pub outgoing_metadata: Vec, + // All outgoing sapling sends to addresses outside this wallet + pub outgoing_metadata_change: Vec, + // Whether this TxID was downloaded from the server and scanned for Memos pub full_tx_scanned: bool, + + // Value Balance of this Tx. + pub value_balance : u64, } impl WalletTx { pub fn serialized_version() -> u64 { - return 4; + return 6; } pub fn new(height: i32, datetime: u64, txid: &TxId) -> Self { @@ -406,7 +412,9 @@ impl WalletTx { total_shielded_value_spent: 0, total_transparent_value_spent: 0, outgoing_metadata: vec![], + outgoing_metadata_change: vec![], full_tx_scanned: false, + value_balance: 0, } } @@ -436,8 +444,20 @@ impl WalletTx { // Outgoing metadata was only added in version 2 let outgoing_metadata = Vector::read(&mut reader, |r| OutgoingTxMetadata::read(r))?; + let outgoing_metadata_change = if version >= 6 { + Vector::read(&mut reader, |r| OutgoingTxMetadata::read(r))? + } else { + vec![] + }; + let full_tx_scanned = reader.read_u8()? > 0; - + + let value_balance = if version >= 5 { + reader.read_u64::()? + } else { + 0 + }; + Ok(WalletTx{ block, datetime, @@ -447,7 +467,9 @@ impl WalletTx { total_shielded_value_spent, total_transparent_value_spent, outgoing_metadata, - full_tx_scanned + outgoing_metadata_change, + full_tx_scanned, + value_balance }) } @@ -469,8 +491,13 @@ impl WalletTx { // Write the outgoing metadata Vector::write(&mut writer, &self.outgoing_metadata, |w, om| om.write(w))?; + // Write the outgoing metadata_change + Vector::write(&mut writer, &self.outgoing_metadata_change, |w, om| om.write(w))?; + writer.write_u8(if self.full_tx_scanned {1} else {0})?; + writer.write_u64::(self.value_balance)?; + Ok(()) } }