1use std::sync::Arc;
7
8use aztec_core::error::Error;
9use aztec_core::types::{AztecAddress, Fr};
10use serde::{Deserialize, Serialize};
11
12use super::kv::KvStore;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub enum NoteStatus {
17 Active,
19 Nullified,
21 ActiveOrNullified,
23}
24
25impl Default for NoteStatus {
26 fn default() -> Self {
27 Self::Active
28 }
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct StoredNote {
34 pub contract_address: AztecAddress,
36 #[serde(default)]
38 pub owner: AztecAddress,
39 pub storage_slot: Fr,
41 #[serde(default)]
43 pub randomness: Fr,
44 #[serde(default)]
46 pub note_nonce: Fr,
47 pub note_hash: Fr,
49 pub siloed_nullifier: Fr,
51 pub note_data: Vec<Fr>,
53 pub nullified: bool,
55 #[serde(default)]
57 pub is_pending: bool,
58 pub nullification_block_number: Option<u64>,
60 pub leaf_index: Option<u64>,
62 pub block_number: Option<u64>,
64 pub tx_index_in_block: Option<u64>,
66 pub note_index_in_tx: Option<u64>,
68 pub scopes: Vec<AztecAddress>,
70}
71
72#[derive(Debug, Clone, Default)]
74pub struct NoteFilter {
75 pub contract_address: Option<AztecAddress>,
77 pub storage_slot: Option<Fr>,
79 pub owner: Option<AztecAddress>,
81 pub status: NoteStatus,
83 pub scopes: Vec<AztecAddress>,
85 pub siloed_nullifier: Option<Fr>,
87}
88
89pub struct NoteStore {
98 kv: Arc<dyn KvStore>,
99}
100
101impl NoteStore {
102 pub fn new(kv: Arc<dyn KvStore>) -> Self {
103 Self { kv }
104 }
105
106 pub async fn add_notes(&self, notes: &[StoredNote], scope: &AztecAddress) -> Result<(), Error> {
108 for note in notes {
109 let mut stored = note.clone();
110 if !stored.scopes.contains(scope) {
112 stored.scopes.push(*scope);
113 }
114
115 let key = note_key_by_nullifier(&stored.siloed_nullifier);
116
117 if let Some(existing_bytes) = self.kv.get(&key).await? {
119 let mut existing: StoredNote = serde_json::from_slice(&existing_bytes)?;
120 if !existing.scopes.contains(scope) {
121 existing.scopes.push(*scope);
122 }
123 let value = serde_json::to_vec(&existing)?;
124 self.kv.put(&key, &value).await?;
125 } else {
126 let value = serde_json::to_vec(&stored)?;
127 self.kv.put(&key, &value).await?;
128
129 self.add_to_contract_index(&stored.contract_address, &stored.siloed_nullifier)
131 .await?;
132
133 if let Some(bn) = stored.block_number {
135 self.add_to_block_index(bn, &stored.siloed_nullifier)
136 .await?;
137 }
138 }
139 }
140 Ok(())
141 }
142
143 pub async fn add_note(&self, note: &StoredNote) -> Result<(), Error> {
145 let scope = note.scopes.first().copied().unwrap_or(AztecAddress::zero());
146 self.add_notes(&[note.clone()], &scope).await
147 }
148
149 pub async fn get_notes(&self, filter: &NoteFilter) -> Result<Vec<StoredNote>, Error> {
151 let notes = if let Some(ref contract) = filter.contract_address {
152 self.get_notes_for_contract(contract).await?
153 } else {
154 self.get_all_notes().await?
155 };
156
157 let filtered: Vec<StoredNote> = notes
158 .into_iter()
159 .filter(|note| {
160 match filter.status {
162 NoteStatus::Active => {
163 if note.nullified {
164 return false;
165 }
166 }
167 NoteStatus::Nullified => {
168 if !note.nullified {
169 return false;
170 }
171 }
172 NoteStatus::ActiveOrNullified => {}
173 }
174
175 if let Some(ref slot) = filter.storage_slot {
177 if note.storage_slot != *slot {
178 return false;
179 }
180 }
181
182 if let Some(ref owner) = filter.owner {
184 if note.owner != *owner {
185 return false;
186 }
187 }
188
189 if !filter.scopes.is_empty()
191 && !note.scopes.iter().any(|s| filter.scopes.contains(s))
192 {
193 return false;
194 }
195
196 if let Some(ref nullifier) = filter.siloed_nullifier {
198 if note.siloed_nullifier != *nullifier {
199 return false;
200 }
201 }
202
203 true
204 })
205 .collect();
206
207 let mut sorted = filtered;
209 sorted.sort_by(|a, b| {
210 a.block_number
211 .cmp(&b.block_number)
212 .then(a.tx_index_in_block.cmp(&b.tx_index_in_block))
213 .then(a.note_index_in_tx.cmp(&b.note_index_in_tx))
214 });
215
216 Ok(sorted)
217 }
218
219 pub async fn get_notes_by_slot(
221 &self,
222 contract: &AztecAddress,
223 storage_slot: &Fr,
224 ) -> Result<Vec<StoredNote>, Error> {
225 self.get_notes(&NoteFilter {
226 contract_address: Some(*contract),
227 storage_slot: Some(*storage_slot),
228 status: NoteStatus::Active,
229 ..Default::default()
230 })
231 .await
232 }
233
234 pub async fn apply_nullifiers(
236 &self,
237 nullifiers: &[(Fr, u64)], ) -> Result<(), Error> {
239 for (nullifier, block_number) in nullifiers {
240 let key = note_key_by_nullifier(nullifier);
241 if let Some(bytes) = self.kv.get(&key).await? {
242 let mut note: StoredNote = serde_json::from_slice(&bytes)?;
243 if !note.nullified {
244 note.nullified = true;
245 note.nullification_block_number = Some(*block_number);
246 let value = serde_json::to_vec(¬e)?;
247 self.kv.put(&key, &value).await?;
248
249 self.add_to_nullification_block_index(*block_number, nullifier)
251 .await?;
252 }
253 }
254 }
255 Ok(())
256 }
257
258 pub async fn nullify_note(
260 &self,
261 contract: &AztecAddress,
262 storage_slot: &Fr,
263 note_hash: &Fr,
264 ) -> Result<(), Error> {
265 let notes = self.get_notes_for_contract(contract).await?;
267 for note in notes {
268 if note.storage_slot == *storage_slot && note.note_hash == *note_hash {
269 self.apply_nullifiers(&[(note.siloed_nullifier, 0)]).await?;
270 return Ok(());
271 }
272 }
273 Ok(())
274 }
275
276 pub async fn rollback(
278 &self,
279 block_number: u64,
280 _synced_block_number: u64,
281 ) -> Result<(), Error> {
282 let nullification_prefix = b"note_idx:nullify_block:";
284 let entries = self.kv.list_prefix(nullification_prefix).await?;
285
286 for (key, value) in &entries {
287 let key_str = String::from_utf8_lossy(key);
288 if let Some(bn_str) = key_str.strip_prefix("note_idx:nullify_block:") {
289 if let Ok(bn) = bn_str.parse::<u64>() {
290 if bn > block_number {
291 let nullifiers: Vec<String> = serde_json::from_slice(value)?;
292 for nullifier_str in &nullifiers {
293 if let Ok(nullifier) = Fr::from_hex(nullifier_str) {
294 let note_key = note_key_by_nullifier(&nullifier);
295 if let Some(note_bytes) = self.kv.get(¬e_key).await? {
296 let mut note: StoredNote = serde_json::from_slice(¬e_bytes)?;
297 note.nullified = false;
298 note.nullification_block_number = None;
299 self.kv.put(¬e_key, &serde_json::to_vec(¬e)?).await?;
300 }
301 }
302 }
303 self.kv.delete(key).await?;
304 }
305 }
306 }
307 }
308
309 let block_prefix = b"note_idx:block:";
311 let block_entries = self.kv.list_prefix(block_prefix).await?;
312
313 for (key, value) in &block_entries {
314 let key_str = String::from_utf8_lossy(key);
315 if let Some(bn_str) = key_str.strip_prefix("note_idx:block:") {
316 if let Ok(bn) = bn_str.parse::<u64>() {
317 if bn > block_number {
318 let nullifiers: Vec<String> = serde_json::from_slice(value)?;
319 for nullifier_str in &nullifiers {
320 if let Ok(nullifier) = Fr::from_hex(nullifier_str) {
321 let note_key = note_key_by_nullifier(&nullifier);
322 if let Some(note_bytes) = self.kv.get(¬e_key).await? {
324 let note: StoredNote = serde_json::from_slice(¬e_bytes)?;
325 self.remove_from_contract_index(
326 ¬e.contract_address,
327 &nullifier,
328 )
329 .await?;
330 }
331 self.kv.delete(¬e_key).await?;
332 }
333 }
334 self.kv.delete(key).await?;
335 }
336 }
337 }
338 }
339
340 Ok(())
341 }
342
343 pub async fn has_note(&self, contract: &AztecAddress, note_hash: &Fr) -> Result<bool, Error> {
345 let notes = self.get_notes_for_contract(contract).await?;
346 Ok(notes
347 .iter()
348 .any(|n| n.note_hash == *note_hash && !n.nullified))
349 }
350
351 async fn get_notes_for_contract(
354 &self,
355 contract: &AztecAddress,
356 ) -> Result<Vec<StoredNote>, Error> {
357 let idx_key = contract_index_key(contract);
358 let nullifiers: Vec<String> = match self.kv.get(&idx_key).await? {
359 Some(bytes) => serde_json::from_slice(&bytes)?,
360 None => return Ok(vec![]),
361 };
362
363 let mut notes = Vec::new();
364 for nullifier_str in nullifiers {
365 if let Ok(nullifier) = Fr::from_hex(&nullifier_str) {
366 let key = note_key_by_nullifier(&nullifier);
367 if let Some(bytes) = self.kv.get(&key).await? {
368 notes.push(serde_json::from_slice(&bytes)?);
369 }
370 }
371 }
372 Ok(notes)
373 }
374
375 async fn get_all_notes(&self) -> Result<Vec<StoredNote>, Error> {
376 let prefix = b"note:";
377 let entries = self.kv.list_prefix(prefix).await?;
378 entries
379 .into_iter()
380 .map(|(_, v)| Ok(serde_json::from_slice(&v)?))
381 .collect()
382 }
383
384 async fn add_to_contract_index(
385 &self,
386 contract: &AztecAddress,
387 nullifier: &Fr,
388 ) -> Result<(), Error> {
389 let key = contract_index_key(contract);
390 let mut list: Vec<String> = match self.kv.get(&key).await? {
391 Some(bytes) => serde_json::from_slice(&bytes)?,
392 None => vec![],
393 };
394 let nullifier_str = format!("{nullifier}");
395 if !list.contains(&nullifier_str) {
396 list.push(nullifier_str);
397 self.kv.put(&key, &serde_json::to_vec(&list)?).await?;
398 }
399 Ok(())
400 }
401
402 async fn remove_from_contract_index(
403 &self,
404 contract: &AztecAddress,
405 nullifier: &Fr,
406 ) -> Result<(), Error> {
407 let key = contract_index_key(contract);
408 if let Some(bytes) = self.kv.get(&key).await? {
409 let mut list: Vec<String> = serde_json::from_slice(&bytes)?;
410 let nullifier_str = format!("{nullifier}");
411 list.retain(|s| s != &nullifier_str);
412 if list.is_empty() {
413 self.kv.delete(&key).await?;
414 } else {
415 self.kv.put(&key, &serde_json::to_vec(&list)?).await?;
416 }
417 }
418 Ok(())
419 }
420
421 async fn add_to_block_index(&self, block_number: u64, nullifier: &Fr) -> Result<(), Error> {
422 let key = block_index_key(block_number);
423 let mut list: Vec<String> = match self.kv.get(&key).await? {
424 Some(bytes) => serde_json::from_slice(&bytes)?,
425 None => vec![],
426 };
427 let nullifier_str = format!("{nullifier}");
428 if !list.contains(&nullifier_str) {
429 list.push(nullifier_str);
430 self.kv.put(&key, &serde_json::to_vec(&list)?).await?;
431 }
432 Ok(())
433 }
434
435 async fn add_to_nullification_block_index(
436 &self,
437 block_number: u64,
438 nullifier: &Fr,
439 ) -> Result<(), Error> {
440 let key = nullification_block_index_key(block_number);
441 let mut list: Vec<String> = match self.kv.get(&key).await? {
442 Some(bytes) => serde_json::from_slice(&bytes)?,
443 None => vec![],
444 };
445 let nullifier_str = format!("{nullifier}");
446 if !list.contains(&nullifier_str) {
447 list.push(nullifier_str);
448 self.kv.put(&key, &serde_json::to_vec(&list)?).await?;
449 }
450 Ok(())
451 }
452}
453
454fn note_key_by_nullifier(nullifier: &Fr) -> Vec<u8> {
455 format!("note:{nullifier}").into_bytes()
456}
457
458fn contract_index_key(contract: &AztecAddress) -> Vec<u8> {
459 format!("note_idx:contract:{contract}").into_bytes()
460}
461
462fn block_index_key(block_number: u64) -> Vec<u8> {
463 format!("note_idx:block:{block_number}").into_bytes()
464}
465
466fn nullification_block_index_key(block_number: u64) -> Vec<u8> {
467 format!("note_idx:nullify_block:{block_number}").into_bytes()
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473 use crate::stores::InMemoryKvStore;
474
475 fn make_note(contract: u64, slot: u64, hash: u64, nullifier: u64) -> StoredNote {
476 StoredNote {
477 contract_address: AztecAddress::from(contract),
478 owner: AztecAddress::from(99u64),
479 storage_slot: Fr::from(slot),
480 randomness: Fr::from(7u64),
481 note_nonce: Fr::from(11u64),
482 note_hash: Fr::from(hash),
483 siloed_nullifier: Fr::from(nullifier),
484 note_data: vec![Fr::from(10u64), Fr::from(20u64)],
485 nullified: false,
486 is_pending: false,
487 nullification_block_number: None,
488 leaf_index: None,
489 block_number: Some(1),
490 tx_index_in_block: Some(0),
491 note_index_in_tx: Some(0),
492 scopes: vec![],
493 }
494 }
495
496 #[tokio::test]
497 async fn add_and_get_notes() {
498 let kv = Arc::new(InMemoryKvStore::new());
499 let store = NoteStore::new(kv);
500 let scope = AztecAddress::from(99u64);
501 let note = make_note(1, 5, 100, 200);
502
503 store.add_notes(&[note], &scope).await.unwrap();
504
505 let notes = store
506 .get_notes(&NoteFilter {
507 contract_address: Some(AztecAddress::from(1u64)),
508 storage_slot: Some(Fr::from(5u64)),
509 ..Default::default()
510 })
511 .await
512 .unwrap();
513 assert_eq!(notes.len(), 1);
514 assert_eq!(notes[0].note_hash, Fr::from(100u64));
515 assert!(notes[0].scopes.contains(&scope));
516 }
517
518 #[tokio::test]
519 async fn apply_nullifiers_and_filter() {
520 let kv = Arc::new(InMemoryKvStore::new());
521 let store = NoteStore::new(kv);
522 let scope = AztecAddress::from(99u64);
523 let note = make_note(1, 5, 100, 200);
524
525 store.add_notes(&[note], &scope).await.unwrap();
526 store
527 .apply_nullifiers(&[(Fr::from(200u64), 5)])
528 .await
529 .unwrap();
530
531 let active = store
533 .get_notes(&NoteFilter {
534 contract_address: Some(AztecAddress::from(1u64)),
535 status: NoteStatus::Active,
536 ..Default::default()
537 })
538 .await
539 .unwrap();
540 assert!(active.is_empty());
541
542 let nullified = store
544 .get_notes(&NoteFilter {
545 contract_address: Some(AztecAddress::from(1u64)),
546 status: NoteStatus::Nullified,
547 ..Default::default()
548 })
549 .await
550 .unwrap();
551 assert_eq!(nullified.len(), 1);
552 assert_eq!(nullified[0].nullification_block_number, Some(5));
553 }
554
555 #[tokio::test]
556 async fn rollback_un_nullifies_notes() {
557 let kv = Arc::new(InMemoryKvStore::new());
558 let store = NoteStore::new(kv);
559 let scope = AztecAddress::from(99u64);
560 let note = make_note(1, 5, 100, 200);
561
562 store.add_notes(&[note], &scope).await.unwrap();
563 store
564 .apply_nullifiers(&[(Fr::from(200u64), 10)])
565 .await
566 .unwrap();
567
568 store.rollback(5, 5).await.unwrap();
570
571 let active = store
572 .get_notes(&NoteFilter {
573 contract_address: Some(AztecAddress::from(1u64)),
574 status: NoteStatus::Active,
575 ..Default::default()
576 })
577 .await
578 .unwrap();
579 assert_eq!(active.len(), 1);
580 assert!(!active[0].nullified);
581 }
582
583 #[tokio::test]
584 async fn scope_filtering() {
585 let kv = Arc::new(InMemoryKvStore::new());
586 let store = NoteStore::new(kv);
587 let scope1 = AztecAddress::from(1u64);
588 let scope2 = AztecAddress::from(2u64);
589 let note = make_note(10, 5, 100, 200);
590
591 store.add_notes(&[note], &scope1).await.unwrap();
592
593 let notes = store
595 .get_notes(&NoteFilter {
596 contract_address: Some(AztecAddress::from(10u64)),
597 scopes: vec![scope1],
598 ..Default::default()
599 })
600 .await
601 .unwrap();
602 assert_eq!(notes.len(), 1);
603
604 let notes = store
606 .get_notes(&NoteFilter {
607 contract_address: Some(AztecAddress::from(10u64)),
608 scopes: vec![scope2],
609 ..Default::default()
610 })
611 .await
612 .unwrap();
613 assert!(notes.is_empty());
614 }
615
616 #[tokio::test]
617 async fn backward_compat_add_and_nullify() {
618 let kv = Arc::new(InMemoryKvStore::new());
619 let store = NoteStore::new(kv);
620 let note = make_note(1, 5, 100, 200);
621
622 store.add_note(¬e).await.unwrap();
623 let found = store
624 .has_note(&AztecAddress::from(1u64), &Fr::from(100u64))
625 .await
626 .unwrap();
627 assert!(found);
628
629 store
630 .nullify_note(
631 &AztecAddress::from(1u64),
632 &Fr::from(5u64),
633 &Fr::from(100u64),
634 )
635 .await
636 .unwrap();
637
638 let found = store
639 .has_note(&AztecAddress::from(1u64), &Fr::from(100u64))
640 .await
641 .unwrap();
642 assert!(!found);
643 }
644}