update wallet and transaction list json format

This commit is contained in:
Cryptoforge 2020-08-05 22:08:22 -07:00
parent 041dc38b67
commit 546196e756
3 changed files with 236 additions and 112 deletions

View File

@ -870,78 +870,112 @@ impl LightClient {
.flat_map(| (_k, v) | { .flat_map(| (_k, v) | {
let mut txns: Vec<JsonValue> = vec![]; let mut txns: Vec<JsonValue> = vec![];
if v.total_shielded_value_spent + v.total_transparent_value_spent > 0 { //Get totals from outgoing metadata
// If money was spent, create a transaction. For this, we'll subtract let total_change: u64 = v.outgoing_metadata_change.iter().map(|u| u.value).sum::<u64>();
// all the change notes. TODO: Add transparent change here to subtract it also let total_send: u64 = v.outgoing_metadata.iter().map(|u| u.value).sum::<u64>();
let total_change: u64 = v.notes.iter()
.filter( |nd| nd.is_change )
.map( |nd| nd.note.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::<Vec<String>>();
// Collect outgoing metadata // Collect incoming metadata
let outgoing_json = v.outgoing_metadata.iter() let mut incoming_json = v.notes.iter()
.map(|om|
object!{
"address" => om.address.clone(),
"value" => om.value,
"memo" => LightWallet::memo_str(&Some(om.memo.clone())),
})
.collect::<Vec<JsonValue>>();
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()
.filter( |nd| !nd.is_change ) .filter( |nd| !nd.is_change )
.enumerate() .enumerate()
.map ( |(i, nd)| .map ( |(_i, nd)|
object! { 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), "address" => LightWallet::note_address(self.config.hrp_sapling_address(), nd),
"value" => nd.note.value as i64,
"memo" => LightWallet::memo_str(&nd.memo), "memo" => LightWallet::memo_str(&nd.memo),
}) })
); .collect::<Vec<JsonValue>>();
// Get the total transparent received let incoming_t_json = v.utxos.iter()
let total_transparent_received = v.utxos.iter().map(|u| u.value).sum::<u64>(); .filter(|u| !change_addresses.contains(&u.address))
if total_transparent_received > v.total_transparent_value_spent { .map( |uo|
// Create an input transaction for the transparent value as well. object! {
txns.push(object!{ "address" => uo.address.clone(),
"block_height" => v.block, "value" => uo.value.clone() as i64,
"datetime" => v.datetime, "memo" => None::<String>,
"txid" => format!("{}", v.txid), })
"amount" => total_transparent_received as i64 - v.total_transparent_value_spent as i64, .collect::<Vec<JsonValue>>();
"address" => v.utxos.iter().map(|u| u.address.clone()).collect::<Vec<String>>().join(","),
"memo" => None::<String> for json in incoming_t_json {
}) incoming_json.push(json.clone());
} }
txns // Collect incoming metadata change
}) let mut incoming_change_json = v.notes.iter()
.collect::<Vec<JsonValue>>(); .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::<Vec<JsonValue>>();
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::<String>,
})
.collect::<Vec<JsonValue>>();
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::<Vec<JsonValue>>();
// 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::<Vec<JsonValue>>();
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::<Vec<JsonValue>>();
// Add in all mempool txns // Add in all mempool txns
tx_list.extend(wallet.mempool_txs.read().unwrap().iter().map( |(_, wtx)| { 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::<u64>(); let amount: u64 = wtx.outgoing_metadata.iter().map(|om| om.value).sum::<u64>();
let fee: u64 = DEFAULT_FEE.try_into().unwrap(); let fee: u64 = wtx.total_shielded_value_spent - amount;
// Collect outgoing metadata // Collect outgoing metadata
let outgoing_json = wtx.outgoing_metadata.iter() let outgoing_json = wtx.outgoing_metadata.iter()
@ -957,6 +991,7 @@ impl LightClient {
"datetime" => wtx.datetime, "datetime" => wtx.datetime,
"txid" => format!("{}", wtx.txid), "txid" => format!("{}", wtx.txid),
"amount" => -1 * (fee + amount) as i64, "amount" => -1 * (fee + amount) as i64,
"fee" => fee as i64,
"unconfirmed" => true, "unconfirmed" => true,
"outgoing_metadata" => outgoing_json, "outgoing_metadata" => outgoing_json,
} }
@ -1351,6 +1386,7 @@ impl LightClient {
info!("Transaction Complete"); info!("Transaction Complete");
match rawtx { match rawtx {
Ok(txbytes) => broadcast_raw_tx(&self.get_server_uri(), txbytes), Ok(txbytes) => broadcast_raw_tx(&self.get_server_uri(), txbytes),
Err(e) => Err(format!("Error: No Tx to broadcast. Error was: {}", e)) Err(e) => Err(format!("Error: No Tx to broadcast. Error was: {}", e))

View File

@ -971,13 +971,24 @@ impl LightWallet {
pub fn scan_full_tx(&self, tx: &Transaction, height: i32, datetime: u64) { pub fn scan_full_tx(&self, tx: &Transaction, height: i32, datetime: u64) {
let mut total_transparent_spend: u64 = 0; 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 // 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. // Find the txid in the list of utxos that we have.
let txid = TxId {0: vin.prevout.hash}; let txid = TxId {0: vin.prevout.hash};
match self.txs.write().unwrap().get_mut(&txid) { match self.txs.write().unwrap().get_mut(&txid) {
Some(wtx) => { Some(wtx) => {
//println!("Looking for {}, {}", txid, vin.prevout.n);
// One of the tx outputs is a match // One of the tx outputs is a match
let spent_utxo = wtx.utxos.iter_mut() let spent_utxo = wtx.utxos.iter_mut()
@ -988,6 +999,7 @@ impl LightWallet {
info!("Spent utxo from {} was spent in {}", txid, tx.txid()); info!("Spent utxo from {} was spent in {}", txid, tx.txid());
su.spent = Some(tx.txid().clone()); su.spent = Some(tx.txid().clone());
su.unconfirmed_spent = None; su.unconfirmed_spent = None;
tinputs.push(su.address.clone());
total_transparent_spend += su.value; 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); 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 { 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 // We spent money in this Tx, so grab all the transparent outputs (except ours) and add them to the
// outgoing metadata // outgoing metadata
// Collect our t-addresses
let wallet_taddrs = self.taddresses.read().unwrap().iter()
.map(|a| a.clone())
.collect::<HashSet<String>>();
for vout in tx.vout.iter() { for vout in tx.vout.iter() {
let taddr = self.address_from_pubkeyhash(vout.script_pubkey.address()); let taddr = self.address_from_pubkeyhash(vout.script_pubkey.address());
//if taddr.is_some() && !wallet_taddrs.contains(&taddr.clone().unwrap()) {
if taddr.is_some() { if taddr.is_some() {
let taddr = taddr.unwrap(); let taddr = taddr.unwrap();
// Add it to outgoing metadata if tinputs.contains(&taddr) {
let mut txs = self.txs.write().unwrap(); outgoing_change.push((taddr, vout.value.into(), Memo::default()));
if txs.get(&tx.txid()).unwrap().outgoing_metadata.iter() } else {
.find(|om| outgoing.push((taddr, vout.value.into(), Memo::default()));
om.address == taddr && Amount::from_u64(om.value).unwrap() == vout.value)
.is_some() {
warn!("Duplicate outgoing metadata");
continue;
} }
// 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 // the memo and value for our records
// First, collect all our z addresses, to check for change // First, collect all our z addresses, to check for change
// Collect z addresses // Collect z addresses spent from
let z_addresses = self.zaddress.read().unwrap().iter().map( |ad| {
encode_payment_address(self.config.hrp_sapling_address(), &ad) let mut zinputs = Vec::new();
}).collect::<HashSet<String>>(); {
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 // Search all ovks that we have
let ovks: Vec<_> = self.extfvks.read().unwrap().iter().map( let ovks: Vec<_> = self.extfvks.read().unwrap().iter().map(
@ -1137,34 +1172,57 @@ impl LightWallet {
&payment_address); &payment_address);
// Check if this is a change address // Check if this is a change address
// if z_addresses.contains(&address) { if zinputs.contains(&address) {
// continue; outgoing_change.push((address, note.value, memo));
// } } else {
outgoing.push((address, note.value, memo));
// 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,
});
} }
}, },
None => {} 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 // Mark this Tx as scanned
@ -1912,6 +1970,8 @@ impl LightWallet {
println!("{}: Transaction created", now() - start_time); println!("{}: Transaction created", now() - start_time);
println!("Transaction ID: {}", tx.txid()); println!("Transaction ID: {}", tx.txid());
// Mark notes as spent. // Mark notes as spent.
{ {
// Mark sapling notes as unconfirmed spent // Mark sapling notes as unconfirmed spent
@ -1961,6 +2021,7 @@ impl LightWallet {
// Create a new WalletTx // Create a new WalletTx
let mut wtx = WalletTx::new(height as i32, now() as u64, &tx.txid()); let mut wtx = WalletTx::new(height as i32, now() as u64, &tx.txid());
wtx.outgoing_metadata = outgoing_metadata; wtx.outgoing_metadata = outgoing_metadata;
wtx.total_shielded_value_spent = total_value + fee;
// Add it into the mempool // Add it into the mempool
mempool_txs.insert(tx.txid(), wtx); mempool_txs.insert(tx.txid(), wtx);

View File

@ -387,13 +387,19 @@ pub struct WalletTx {
// All outgoing sapling sends to addresses outside this wallet // All outgoing sapling sends to addresses outside this wallet
pub outgoing_metadata: Vec<OutgoingTxMetadata>, pub outgoing_metadata: Vec<OutgoingTxMetadata>,
// All outgoing sapling sends to addresses outside this wallet
pub outgoing_metadata_change: Vec<OutgoingTxMetadata>,
// Whether this TxID was downloaded from the server and scanned for Memos // Whether this TxID was downloaded from the server and scanned for Memos
pub full_tx_scanned: bool, pub full_tx_scanned: bool,
// Value Balance of this Tx.
pub value_balance : u64,
} }
impl WalletTx { impl WalletTx {
pub fn serialized_version() -> u64 { pub fn serialized_version() -> u64 {
return 4; return 6;
} }
pub fn new(height: i32, datetime: u64, txid: &TxId) -> Self { pub fn new(height: i32, datetime: u64, txid: &TxId) -> Self {
@ -406,7 +412,9 @@ impl WalletTx {
total_shielded_value_spent: 0, total_shielded_value_spent: 0,
total_transparent_value_spent: 0, total_transparent_value_spent: 0,
outgoing_metadata: vec![], outgoing_metadata: vec![],
outgoing_metadata_change: vec![],
full_tx_scanned: false, full_tx_scanned: false,
value_balance: 0,
} }
} }
@ -436,8 +444,20 @@ impl WalletTx {
// Outgoing metadata was only added in version 2 // Outgoing metadata was only added in version 2
let outgoing_metadata = Vector::read(&mut reader, |r| OutgoingTxMetadata::read(r))?; 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 full_tx_scanned = reader.read_u8()? > 0;
let value_balance = if version >= 5 {
reader.read_u64::<LittleEndian>()?
} else {
0
};
Ok(WalletTx{ Ok(WalletTx{
block, block,
datetime, datetime,
@ -447,7 +467,9 @@ impl WalletTx {
total_shielded_value_spent, total_shielded_value_spent,
total_transparent_value_spent, total_transparent_value_spent,
outgoing_metadata, outgoing_metadata,
full_tx_scanned outgoing_metadata_change,
full_tx_scanned,
value_balance
}) })
} }
@ -469,8 +491,13 @@ impl WalletTx {
// Write the outgoing metadata // Write the outgoing metadata
Vector::write(&mut writer, &self.outgoing_metadata, |w, om| om.write(w))?; 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_u8(if self.full_tx_scanned {1} else {0})?;
writer.write_u64::<LittleEndian>(self.value_balance)?;
Ok(()) Ok(())
} }
} }