diff --git a/rust-lightclient/src/lightclient.rs b/rust-lightclient/src/lightclient.rs index 8f3e6a3..a7280d1 100644 --- a/rust-lightclient/src/lightclient.rs +++ b/rust-lightclient/src/lightclient.rs @@ -148,7 +148,8 @@ impl LightClient { "value" => nd.note.value, "is_change" => nd.is_change, "address" => LightWallet::address_from_extfvk(&nd.extfvk, nd.diversifier), - "spent" => nd.spent.map(|spent_txid| format!("{}", spent_txid)) + "spent" => nd.spent.map(|spent_txid| format!("{}", spent_txid)), + "unconfirmed_spent" => nd.unconfirmed_spent.map(|spent_txid| format!("{}", spent_txid)), } ) }) diff --git a/rust-lightclient/src/lightwallet.rs b/rust-lightclient/src/lightwallet.rs index c7cd331..d909424 100644 --- a/rust-lightclient/src/lightwallet.rs +++ b/rust-lightclient/src/lightwallet.rs @@ -99,9 +99,11 @@ pub struct SaplingNoteData { pub note: Note, witnesses: Vec>, nullifier: [u8; 32], - pub spent: Option, + pub spent: Option, // If this note was confirmed spent + pub unconfirmed_spent: Option, // If this note was spent in a send, but has not yet been confirmed. pub memo: Option, pub is_change: bool, + // TODO: We need to remove the unconfirmed_spent (i.e., set it to None) if the Tx has expired } @@ -162,6 +164,7 @@ impl SaplingNoteData { witnesses: vec![witness], nullifier: nf, spent: None, + unconfirmed_spent: None, memo: None, is_change: output.is_change, } @@ -196,6 +199,9 @@ impl SaplingNoteData { let mut nullifier = [0u8; 32]; reader.read_exact(&mut nullifier)?; + // Note that this is only the spent field, we ignore the unconfirmed_spent field. + // The reason is that unconfirmed spents are only in memory, and we need to get the actual value of spent + // from the blockchain anyway. let spent = Optional::read(&mut reader, |r| { let mut txid_bytes = [0u8; 32]; r.read_exact(&mut txid_bytes)?; @@ -221,6 +227,7 @@ impl SaplingNoteData { witnesses, nullifier, spent, + unconfirmed_spent: None, memo, is_change, }) @@ -334,7 +341,8 @@ struct SpendableNote { impl SpendableNote { fn from(txid: TxId, nd: &SaplingNoteData, anchor_offset: usize) -> Option { - if nd.spent.is_none() { + // Include only notes that haven't been spent, or haven't been included in an unconfirmed spend yet. + if nd.spent.is_none() && nd.unconfirmed_spent.is_none() { let witness = nd.witnesses.get(nd.witnesses.len() - anchor_offset - 1); witness.map(|w| SpendableNote { @@ -602,7 +610,7 @@ impl LightWallet { None => true } }) - .map(|nd| if nd.spent.is_none() { nd.note.value } else { 0 }) + .map(|nd| if nd.spent.is_none() && nd.unconfirmed_spent.is_none() { nd.note.value } else { 0 }) .sum::() } else { 0 @@ -684,6 +692,8 @@ impl LightWallet { let mut txs = self.txs.write().unwrap(); // Create a Vec containing all unspent nullifiers. + // Include only the confirmed spent nullifiers, since unconfirmed ones still need to be included + // during scan_block below. let nfs: Vec<_> = txs .iter() .map(|(txid, tx)| { @@ -751,7 +761,11 @@ impl LightWallet { .iter_mut() .find(|nd| &nd.nullifier[..] == &spend.nf[..]) .unwrap(); + + // Mark the note as spent, and remove the unconfirmed part of it spent_note.spent = Some(tx.txid); + spent_note.unconfirmed_spent = None::; + total_shielded_value_spent += spent_note.note.value; } @@ -904,6 +918,7 @@ impl LightWallet { println!("Transaction ID: {}", tx.txid()); // Mark notes as spent. + // TODO: This is only a non-confirmed spend, and the note should be marked as such. let mut txs = self.txs.write().unwrap(); for selected in notes { let mut spent_note = txs @@ -913,7 +928,7 @@ impl LightWallet { .iter_mut() .find(|nd| &nd.nullifier[..] == &selected.nullifier[..]) .unwrap(); - spent_note.spent = Some(tx.txid()); + spent_note.unconfirmed_spent = Some(tx.txid()); } // Return the encoded transaction, so the caller can send it.