use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicUsize, Ordering};
use miden_protocol::block::BlockNumber;
use miden_protocol::note::{NoteHeader, NoteId, NoteInclusionProof, NoteTag};
use miden_tx::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use miden_tx::utils::sync::RwLock;
use crate::note_transport::{
NoteInfo,
NoteTransportClient,
NoteTransportCursor,
NoteTransportError,
NoteTransportPage,
TransportNote,
};
#[derive(Clone)]
pub struct MockNoteTransportNode {
notes: BTreeMap<NoteTag, Vec<(NoteInfo, NoteTransportCursor)>>,
nonce: u64,
next_sequence: u64,
max_batch: Option<usize>,
proven_notes: BTreeMap<NoteId, BlockNumber>,
}
impl MockNoteTransportNode {
pub fn new() -> Self {
Self {
notes: BTreeMap::default(),
nonce: 1,
next_sequence: 1,
max_batch: None,
proven_notes: BTreeMap::default(),
}
}
pub fn with_max_batch(max_batch: usize) -> Self {
Self {
notes: BTreeMap::default(),
nonce: 1,
next_sequence: 1,
max_batch: Some(max_batch),
proven_notes: BTreeMap::default(),
}
}
pub fn add_note_with_proof(
&mut self,
header: NoteHeader,
details_bytes: Vec<u8>,
inclusion_proof: &NoteInclusionProof,
) {
if self.contains_note(&header.id()) {
return;
}
let block_num = inclusion_proof.location().block_num();
self.proven_notes.insert(header.id(), block_num);
self.add_note_after(header, details_bytes, Some(block_num));
}
fn contains_note(&self, note_id: &NoteId) -> bool {
self.notes.values().flatten().any(|(info, _)| info.header.id() == *note_id)
}
pub fn proven_block(&self, note_id: &NoteId) -> Option<BlockNumber> {
self.proven_notes.get(note_id).copied()
}
pub fn add_note(&mut self, header: NoteHeader, details_bytes: Vec<u8>) {
self.add_note_after(header, details_bytes, None);
}
pub fn add_note_after(
&mut self,
header: NoteHeader,
details_bytes: Vec<u8>,
block_hint: Option<BlockNumber>,
) {
let tag = header.metadata().tag();
let info = NoteInfo { header, details_bytes, block_hint };
let cursor = NoteTransportCursor {
nonce: self.nonce,
sequence: self.next_sequence,
};
self.next_sequence += 1;
self.notes.entry(tag).or_default().push((info, cursor));
}
pub fn add_note_with_tag_key(
&mut self,
tag: NoteTag,
header: NoteHeader,
details_bytes: Vec<u8>,
) {
let info = NoteInfo { header, details_bytes, block_hint: None };
let cursor = NoteTransportCursor {
nonce: self.nonce,
sequence: self.next_sequence,
};
self.next_sequence += 1;
self.notes.entry(tag).or_default().push((info, cursor));
}
pub fn get_notes(
&self,
tags: &[NoteTag],
cursor: Option<NoteTransportCursor>,
) -> (Vec<NoteInfo>, NoteTransportCursor) {
self.notes_up_to(tags, cursor, self.head())
}
fn head(&self) -> NoteTransportCursor {
NoteTransportCursor {
nonce: self.nonce,
sequence: self.next_sequence - 1,
}
}
fn notes_up_to(
&self,
tags: &[NoteTag],
cursor: Option<NoteTransportCursor>,
sync_to: NoteTransportCursor,
) -> (Vec<NoteInfo>, NoteTransportCursor) {
let after = cursor.filter(|cursor| cursor.nonce == self.nonce).map_or(0, |c| c.sequence);
let mut collected: Vec<(NoteInfo, NoteTransportCursor)> = tags
.iter()
.filter_map(|tag| self.notes.get(tag))
.flatten()
.filter(|(_, tcursor)| tcursor.sequence > after && tcursor.sequence <= sync_to.sequence)
.cloned()
.collect();
collected.sort_by_key(|(_, c)| c.sequence);
if let Some(max) = self.max_batch {
collected.truncate(max);
}
let sequence = collected.last().map_or(after, |(_, c)| c.sequence);
let notes = collected.into_iter().map(|(n, _)| n).collect();
(notes, NoteTransportCursor { nonce: self.nonce, sequence })
}
}
impl Default for MockNoteTransportNode {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Default)]
pub struct MockNoteTransportApi {
pub mock_node: Arc<RwLock<MockNoteTransportNode>>,
fetch_tag_counts: Arc<RwLock<Vec<usize>>>,
fetch_bounds: Arc<RwLock<Vec<Option<NoteTransportCursor>>>>,
}
impl MockNoteTransportApi {
pub fn new(mock_node: Arc<RwLock<MockNoteTransportNode>>) -> Self {
Self {
mock_node,
fetch_tag_counts: Arc::default(),
fetch_bounds: Arc::default(),
}
}
pub fn fetch_tag_counts(&self) -> Vec<usize> {
self.fetch_tag_counts.read().clone()
}
pub fn fetch_bounds(&self) -> Vec<Option<NoteTransportCursor>> {
self.fetch_bounds.read().clone()
}
}
impl MockNoteTransportApi {
pub fn send_note_with_proof(&self, note: TransportNote, inclusion_proof: &NoteInclusionProof) {
let (header, details) = note.into_parts();
let details_bytes = details.to_bytes();
self.mock_node
.write()
.add_note_with_proof(header, details_bytes, inclusion_proof);
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
impl NoteTransportClient for MockNoteTransportApi {
async fn send_note_with_proof(
&self,
note: TransportNote,
inclusion_proof: NoteInclusionProof,
) -> Result<(), NoteTransportError> {
self.send_note_with_proof(note, &inclusion_proof);
Ok(())
}
async fn fetch_notes_page(
&self,
tags: &[NoteTag],
cursor: Option<NoteTransportCursor>,
sync_to: Option<NoteTransportCursor>,
) -> Result<NoteTransportPage, NoteTransportError> {
self.fetch_tag_counts.write().push(tags.len());
self.fetch_bounds.write().push(sync_to);
let node = self.mock_node.read();
let sync_to = sync_to.unwrap_or_else(|| node.head());
let (notes, cursor) = node.notes_up_to(tags, cursor, sync_to);
let has_more = !node.notes_up_to(tags, Some(cursor), sync_to).0.is_empty();
Ok(NoteTransportPage { notes, cursor, has_more, sync_to })
}
}
pub struct FaultyNoteTransportApi {
inner: MockNoteTransportApi,
fail_next: AtomicUsize,
send_attempts: AtomicUsize,
fail_next_fetches: AtomicUsize,
fail_on_fetch: AtomicUsize,
fetch_attempts: AtomicUsize,
}
impl FaultyNoteTransportApi {
pub fn new(mock_node: Arc<RwLock<MockNoteTransportNode>>, fail_next: usize) -> Self {
Self {
inner: MockNoteTransportApi::new(mock_node),
fail_next: AtomicUsize::new(fail_next),
send_attempts: AtomicUsize::new(0),
fail_next_fetches: AtomicUsize::new(0),
fail_on_fetch: AtomicUsize::new(0),
fetch_attempts: AtomicUsize::new(0),
}
}
pub fn send_attempts(&self) -> usize {
self.send_attempts.load(Ordering::SeqCst)
}
pub fn fail_next_n_fetches(&self, n: usize) {
self.fail_next_fetches.store(n, Ordering::SeqCst);
}
pub fn fail_on_fetch_attempt(&self, attempt: usize) {
self.fail_on_fetch.store(attempt, Ordering::SeqCst);
}
pub fn fetch_attempts(&self) -> usize {
self.fetch_attempts.load(Ordering::SeqCst)
}
fn take_send_failure(&self) -> Option<NoteTransportError> {
self.send_attempts.fetch_add(1, Ordering::SeqCst);
self.fail_next
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |n| n.checked_sub(1))
.is_ok()
.then(|| {
NoteTransportError::Network(
"FaultyNoteTransportApi: simulated send_note_with_proof failure".to_string(),
)
})
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
impl NoteTransportClient for FaultyNoteTransportApi {
async fn send_note_with_proof(
&self,
note: TransportNote,
inclusion_proof: NoteInclusionProof,
) -> Result<(), NoteTransportError> {
if let Some(error) = self.take_send_failure() {
return Err(error);
}
self.inner.send_note_with_proof(note, &inclusion_proof);
Ok(())
}
async fn fetch_notes_page(
&self,
tags: &[NoteTag],
cursor: Option<NoteTransportCursor>,
sync_to: Option<NoteTransportCursor>,
) -> Result<NoteTransportPage, NoteTransportError> {
let attempt = self.fetch_attempts.fetch_add(1, Ordering::SeqCst) + 1;
let should_fail = self
.fail_next_fetches
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |n| n.checked_sub(1))
.is_ok();
if should_fail || attempt == self.fail_on_fetch.load(Ordering::SeqCst) {
return Err(NoteTransportError::Network(
"FaultyNoteTransportApi: simulated fetch_notes_page failure".to_string(),
));
}
self.inner.fetch_notes_page(tags, cursor, sync_to).await
}
}
impl Serializable for MockNoteTransportNode {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.notes.write_into(target);
self.nonce.write_into(target);
self.next_sequence.write_into(target);
}
}
impl Deserializable for MockNoteTransportNode {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let notes = BTreeMap::<NoteTag, Vec<(NoteInfo, NoteTransportCursor)>>::read_from(source)?;
let nonce = u64::read_from(source)?;
let next_sequence = u64::read_from(source)?;
Ok(Self {
notes,
nonce,
next_sequence,
max_batch: None,
proven_notes: BTreeMap::default(),
})
}
}