wallet: Move Orchard note position data into a separate map

This commit is contained in:
Jack Grigg 2022-03-30 23:00:33 +00:00
parent 0255964559
commit ac3229201f
1 changed files with 50 additions and 46 deletions

View File

@ -58,22 +58,26 @@ pub struct InPoint {
pub struct DecryptedNote { pub struct DecryptedNote {
note: Note, note: Note,
memo: [u8; 512], memo: [u8; 512],
/// The position of the note's commitment within the global Merkle tree.
position: Option<Position>,
} }
/// A data structure tracking the note data that was decrypted from a single transaction, /// A data structure tracking the note data that was decrypted from a single transaction.
/// along with the height at which that transaction was discovered in the chain.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TxNotes { pub struct TxNotes {
/// The block height containing the transaction from which these
/// notes were decrypted.
tx_height: Option<BlockHeight>,
/// A map from the index of the Orchard action from which this note /// A map from the index of the Orchard action from which this note
/// was decrypted to the decrypted note value. /// was decrypted to the decrypted note value.
decrypted_notes: BTreeMap<usize, DecryptedNote>, decrypted_notes: BTreeMap<usize, DecryptedNote>,
} }
/// A data structure chain position information for a single transaction.
#[derive(Clone, Debug, Default)]
struct NotePositions {
/// The block height containing the transaction (if mined).
tx_height: Option<BlockHeight>,
/// A map from the index of an Orchard action tracked by this wallet, to the position
/// of the note's commitment within the global Merkle tree.
note_positions: BTreeMap<usize, Position>,
}
struct KeyStore { struct KeyStore {
payment_addresses: BTreeMap<OrderedAddress, IncomingViewingKey>, payment_addresses: BTreeMap<OrderedAddress, IncomingViewingKey>,
viewing_keys: BTreeMap<IncomingViewingKey, FullViewingKey>, viewing_keys: BTreeMap<IncomingViewingKey, FullViewingKey>,
@ -136,6 +140,9 @@ pub struct Wallet {
/// The in-memory index from txid to notes from the associated transaction that have /// The in-memory index from txid to notes from the associated transaction that have
/// been decrypted with the IVKs known to this wallet. /// been decrypted with the IVKs known to this wallet.
wallet_received_notes: BTreeMap<TxId, TxNotes>, wallet_received_notes: BTreeMap<TxId, TxNotes>,
/// The in-memory index from txid to note positions from the associated transaction.
/// This map should always be a subset of `wallet_received_notes`.
wallet_note_positions: BTreeMap<TxId, NotePositions>,
/// The in-memory index from nullifier to the outpoint of the note from which that /// The in-memory index from nullifier to the outpoint of the note from which that
/// nullifier was derived. /// nullifier was derived.
nullifiers: BTreeMap<Nullifier, OutPoint>, nullifiers: BTreeMap<Nullifier, OutPoint>,
@ -209,6 +216,7 @@ impl Wallet {
Wallet { Wallet {
key_store: KeyStore::empty(), key_store: KeyStore::empty(),
wallet_received_notes: BTreeMap::new(), wallet_received_notes: BTreeMap::new(),
wallet_note_positions: BTreeMap::new(),
nullifiers: BTreeMap::new(), nullifiers: BTreeMap::new(),
witness_tree: BridgeTree::new(MAX_CHECKPOINTS), witness_tree: BridgeTree::new(MAX_CHECKPOINTS),
last_checkpoint: None, last_checkpoint: None,
@ -224,8 +232,9 @@ impl Wallet {
/// in place with the expectation that they will be overwritten and/or updated in /// in place with the expectation that they will be overwritten and/or updated in
/// the rescan process. /// the rescan process.
pub fn reset(&mut self) { pub fn reset(&mut self) {
for tx_notes in self.wallet_received_notes.values_mut() { for tx_notes in self.wallet_note_positions.values_mut() {
tx_notes.tx_height = None; tx_notes.tx_height = None;
tx_notes.note_positions.clear();
} }
self.witness_tree = BridgeTree::new(MAX_CHECKPOINTS); self.witness_tree = BridgeTree::new(MAX_CHECKPOINTS);
self.last_checkpoint = None; self.last_checkpoint = None;
@ -296,7 +305,7 @@ impl Wallet {
// retain notes that correspond to transactions that are not "un-mined" after // retain notes that correspond to transactions that are not "un-mined" after
// the rewind // the rewind
let to_retain: BTreeSet<_> = self let to_retain: BTreeSet<_> = self
.wallet_received_notes .wallet_note_positions
.iter() .iter()
.filter_map(|(txid, n)| { .filter_map(|(txid, n)| {
if n.tx_height if n.tx_height
@ -314,14 +323,12 @@ impl Wallet {
// once we've observed a note for the first time. The block height at which we // once we've observed a note for the first time. The block height at which we
// observed the note is set to `None` as the transaction will no longer have been // observed the note is set to `None` as the transaction will no longer have been
// observed as having been mined. // observed as having been mined.
for (txid, n) in self.wallet_received_notes.iter_mut() { for (txid, n) in self.wallet_note_positions.iter_mut() {
// Erase block height and commitment tree information for any received // Erase block height and commitment tree information for any received
// notes that have been un-mined by the rewind. // notes that have been un-mined by the rewind.
if !to_retain.contains(txid) { if !to_retain.contains(txid) {
n.tx_height = None; n.tx_height = None;
for dnote in n.decrypted_notes.values_mut() { n.note_positions.clear();
dnote.position = None;
}
} }
} }
self.last_observed = Some(last_observed); self.last_observed = Some(last_observed);
@ -360,15 +367,7 @@ impl Wallet {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for (action_idx, ivk, note, recipient, memo) in bundle.decrypt_outputs_with_keys(&keys) { for (action_idx, ivk, note, recipient, memo) in bundle.decrypt_outputs_with_keys(&keys) {
assert!(self.add_decrypted_note( assert!(self.add_decrypted_note(txid, action_idx, ivk.clone(), note, recipient, memo));
None,
txid,
action_idx,
ivk.clone(),
note,
recipient,
memo
));
involvement.receive_action_metadata.insert(action_idx, ivk); involvement.receive_action_metadata.insert(action_idx, ivk);
} }
@ -414,21 +413,20 @@ impl Wallet {
for (action_idx, ivk) in hints.into_iter() { for (action_idx, ivk) in hints.into_iter() {
if let Some((note, recipient, memo)) = bundle.decrypt_output_with_key(action_idx, ivk) { if let Some((note, recipient, memo)) = bundle.decrypt_output_with_key(action_idx, ivk) {
if !self.add_decrypted_note( if !self.add_decrypted_note(txid, action_idx, ivk.clone(), note, recipient, memo) {
tx_height,
txid,
action_idx,
ivk.clone(),
note,
recipient,
memo,
) {
return Err(BundleLoadError::FvkNotFound(ivk.clone())); return Err(BundleLoadError::FvkNotFound(ivk.clone()));
} }
} else { } else {
return Err(BundleLoadError::ActionDecryptionFailed(action_idx)); return Err(BundleLoadError::ActionDecryptionFailed(action_idx));
} }
} }
// Set the transaction's height.
self.wallet_note_positions
.entry(*txid)
.or_default()
.tx_height = tx_height;
Ok(()) Ok(())
} }
@ -436,7 +434,6 @@ impl Wallet {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn add_decrypted_note( fn add_decrypted_note(
&mut self, &mut self,
tx_height: Option<BlockHeight>,
txid: &TxId, txid: &TxId,
action_idx: usize, action_idx: usize,
ivk: IncomingViewingKey, ivk: IncomingViewingKey,
@ -458,15 +455,10 @@ impl Wallet {
self.nullifiers.insert(nf, outpoint); self.nullifiers.insert(nf, outpoint);
// add the decrypted note data to the wallet // add the decrypted note data to the wallet
let note_data = DecryptedNote { let note_data = DecryptedNote { note, memo };
note,
memo,
position: None,
};
self.wallet_received_notes self.wallet_received_notes
.entry(*txid) .entry(*txid)
.or_insert_with(|| TxNotes { .or_insert_with(|| TxNotes {
tx_height,
decrypted_notes: BTreeMap::new(), decrypted_notes: BTreeMap::new(),
}) })
.decrypted_notes .decrypted_notes
@ -560,11 +552,14 @@ impl Wallet {
}); });
// update the block height recorded for the transaction // update the block height recorded for the transaction
if let Some(tx_notes) = self.wallet_received_notes.get_mut(txid) { let mut my_notes_for_tx = self.wallet_received_notes.get_mut(txid);
tx_notes.tx_height = Some(block_height); if my_notes_for_tx.is_some() {
self.wallet_note_positions
.entry(*txid)
.or_default()
.tx_height = Some(block_height);
} }
let mut my_notes_for_tx = self.wallet_received_notes.get_mut(txid);
for (action_idx, action) in bundle.actions().iter().enumerate() { for (action_idx, action) in bundle.actions().iter().enumerate() {
// append the note commitment for each action to the witness tree // append the note commitment for each action to the witness tree
if !self if !self
@ -575,13 +570,18 @@ impl Wallet {
} }
// for notes that are ours, witness the current state of the tree // for notes that are ours, witness the current state of the tree
if let Some(dnote) = my_notes_for_tx if my_notes_for_tx
.as_mut() .as_mut()
.and_then(|n| n.decrypted_notes.get_mut(&action_idx)) .and_then(|n| n.decrypted_notes.get_mut(&action_idx))
.is_some()
{ {
let (pos, cmx) = self.witness_tree.witness().expect("tree is not empty"); let (pos, cmx) = self.witness_tree.witness().expect("tree is not empty");
assert_eq!(cmx, MerkleHashOrchard::from_cmx(action.cmx())); assert_eq!(cmx, MerkleHashOrchard::from_cmx(action.cmx()));
dnote.position = Some(pos); self.wallet_note_positions
.entry(*txid)
.or_default()
.note_positions
.insert(action_idx, pos);
} }
// For nullifiers that are ours that we detect as spent by this action, // For nullifiers that are ours that we detect as spent by this action,
@ -663,12 +663,16 @@ impl Wallet {
self.key_store self.key_store
.ivk_for_address(&dnote.note.recipient()) .ivk_for_address(&dnote.note.recipient())
.and_then(|ivk| self.key_store.viewing_keys.get(ivk)) .and_then(|ivk| self.key_store.viewing_keys.get(ivk))
.zip(dnote.position) .zip(
self.wallet_note_positions
.get(&outpoint.txid)
.and_then(|tx_notes| tx_notes.note_positions.get(&outpoint.action_idx)),
)
.map(|(fvk, position)| { .map(|(fvk, position)| {
let path = self let path = self
.witness_tree .witness_tree
.authentication_path( .authentication_path(
position, *position,
&MerkleHashOrchard::from_cmx(&dnote.note.commitment().into()), &MerkleHashOrchard::from_cmx(&dnote.note.commitment().into()),
) )
.expect("wallet always has paths to positioned notes"); .expect("wallet always has paths to positioned notes");
@ -676,7 +680,7 @@ impl Wallet {
fvk.clone(), fvk.clone(),
dnote.note, dnote.note,
MerklePath::from_parts( MerklePath::from_parts(
u64::from(position).try_into().unwrap(), u64::from(*position).try_into().unwrap(),
path.try_into().unwrap(), path.try_into().unwrap(),
), ),
) )