miden_client/sync/mod.rs
1//! Provides the client APIs for synchronizing the client's local state with the Miden network. It
2//! ensures that the client maintains a valid, up-to-date view of the chain.
3//!
4//! ## Overview
5//!
6//! This module handles the synchronization process between the local client and the Miden network.
7//! The sync operation involves:
8//!
9//! - Querying the Miden node for state updates using tracked account IDs, note tags, and nullifier
10//! prefixes.
11//! - Processing the received data to update note inclusion proofs, reconcile note state (new,
12//! committed, or consumed), and update account states.
13//! - Incorporating new block headers and updating the local Merkle Mountain Range (MMR) with new
14//! peaks and authentication nodes.
15//! - Aggregating transaction updates to determine which transactions have been committed or
16//! discarded.
17//!
18//! The result of the synchronization process is captured in a [`SyncSummary`], which provides a
19//! summary of the new block number along with lists of received, committed, and consumed note IDs,
20//! updated account IDs, locked accounts, and committed transaction IDs.
21//!
22//! Once the data is requested and retrieved, updates are persisted in the client's store.
23//!
24//! ## Examples
25//!
26//! The following example shows how to initiate a state sync and handle the resulting summary:
27//!
28//! ```rust
29//! # use miden_client::auth::TransactionAuthenticator;
30//! # use miden_client::sync::SyncSummary;
31//! # use miden_client::{Client, ClientError};
32//! # use miden_protocol::{block::BlockHeader, Felt, Word};
33//! # use miden_protocol::crypto::rand::FeltRng;
34//! # async fn run_sync<AUTH: TransactionAuthenticator + Sync + 'static>(client: &mut Client<AUTH>) -> Result<(), ClientError> {
35//! // Attempt to synchronize the client's state with the Miden network.
36//! // The requested data is based on the client's state: it gets updates for accounts, relevant
37//! // notes, etc. For more information on the data that gets requested, see the doc comments for
38//! // `sync_state()`.
39//! let sync_summary: SyncSummary = client.sync_state().await?;
40//!
41//! println!("Synced up to block number: {}", sync_summary.block_num);
42//! println!("New private notes: {}", sync_summary.new_private_notes.len());
43//! println!("Committed notes: {}", sync_summary.committed_notes.len());
44//! println!("Consumed notes: {}", sync_summary.consumed_notes.len());
45//! println!("Updated accounts: {}", sync_summary.updated_accounts.len());
46//! println!("Locked accounts: {}", sync_summary.locked_accounts.len());
47//! println!("Committed transactions: {}", sync_summary.committed_transactions.len());
48//!
49//! Ok(())
50//! # }
51//! ```
52//!
53//! The `sync_state` method loops internally until the client is fully synced to the network tip.
54//!
55//! For more advanced usage, refer to the individual functions (such as `committed_note_updates` and
56//! `consumed_note_updates`) to understand how the sync data is processed and applied to the local
57//! store.
58
59use alloc::collections::BTreeSet;
60use alloc::format;
61use alloc::sync::Arc;
62use alloc::vec::Vec;
63use core::cmp::max;
64
65use futures::{StreamExt, TryStreamExt};
66use miden_protocol::account::AccountId;
67use miden_protocol::block::account_tree::AccountWitness;
68use miden_protocol::block::{BlockHeader, BlockNumber};
69use miden_protocol::crypto::merkle::mmr::{InOrderIndex, PartialMmr};
70use miden_protocol::note::NoteId;
71use miden_protocol::transaction::TransactionId;
72use miden_tx::auth::TransactionAuthenticator;
73use miden_tx::utils::serde::{Deserializable, DeserializationError, Serializable};
74use tracing::{info, warn};
75
76use crate::pswap::PswapChainObserver;
77use crate::rpc::AccountStateAt;
78use crate::rpc::domain::account::GetAccountRequest;
79use crate::store::{NoteFilter, TransactionFilter};
80use crate::{Client, ClientError};
81mod block_header;
82
83mod tag;
84pub use tag::{NoteTagRecord, NoteTagSource};
85
86mod note_observer;
87pub use note_observer::NoteObserver;
88
89mod state_sync;
90pub use state_sync::{ChainSyncData, NoteUpdateAction, OnNoteReceived, StateSync, StateSyncInput};
91pub(crate) use state_sync::{
92 MAX_CONCURRENT_ACCOUNT_FETCHES,
93 block_num_from_forest,
94 validate_account_witness,
95};
96
97mod state_sync_update;
98pub use state_sync_update::{
99 AccountUpdates,
100 PartialBlockchainUpdates,
101 PublicAccountUpdate,
102 StateSyncUpdate,
103 TransactionUpdateTracker,
104};
105
106/// Untracks the given block leaves from `partial_mmr`, returning the authentication-node indices
107/// that are no longer needed by any remaining tracked leaf.
108///
109/// Untracking a leaf frees an inner node only once no other tracked leaf still needs it, so the
110/// returned indices are exactly the nodes that became removable.
111fn untrack_blocks(
112 partial_mmr: &mut PartialMmr,
113 block_positions: impl IntoIterator<Item = usize>,
114) -> Vec<InOrderIndex> {
115 block_positions
116 .into_iter()
117 .flat_map(|block_pos| partial_mmr.untrack(block_pos))
118 .map(|(index, _)| index)
119 .collect()
120}
121
122/// Client synchronization methods.
123impl<AUTH> Client<AUTH>
124where
125 AUTH: TransactionAuthenticator + Sync + 'static,
126{
127 // SYNC STATE
128 // --------------------------------------------------------------------------------------------
129
130 /// Returns the block number of the last state sync block.
131 pub async fn get_sync_height(&self) -> Result<BlockNumber, ClientError> {
132 self.store.get_sync_height().await.map_err(Into::into)
133 }
134
135 /// Syncs the client's on-chain state with the current state of the Miden network and returns a
136 /// [`SyncSummary`] corresponding to the local state update.
137 ///
138 /// Does **not** fetch private notes from the Note Transport Layer. Use [`Client::sync_state`]
139 /// for the combined sync, or call [`Client::sync_note_transport`] separately.
140 ///
141 /// Fetches everything from the node first ([`Client::fetch_chain_updates`] and
142 /// [`StateSync::fetch_nullifiers`]), then applies the result with
143 /// [`Client::apply_chain_updates`], which also caches the partial MMR and prunes irrelevant
144 /// blocks according to the configured cadence.
145 pub async fn sync_chain(&mut self) -> Result<SyncSummary, ClientError> {
146 self.ensure_genesis_in_place().await?;
147 self.ensure_rpc_limits_in_place().await?;
148
149 let state_sync = self.state_sync().await?;
150 let mut chain_sync_data = self.fetch_chain_updates(&state_sync).await?;
151 state_sync.derive_state_updates(&mut chain_sync_data).await?;
152 state_sync.fetch_nullifiers(&mut chain_sync_data).await?;
153
154 self.apply_chain_updates(&state_sync, chain_sync_data).await
155 }
156
157 /// Fetches the node's view of everything that changed since the client's chain tip, without
158 /// storing anything or modifying the partial MMR.
159 ///
160 /// Builds the default sync input and runs [`StateSync::fetch_state`], then adds the account
161 /// witnesses the registered accounts still need. The state updates must be derived with
162 /// [`StateSync::derive_state_updates`]. The nullifier check is not part of this: run
163 /// [`StateSync::fetch_nullifiers`] on the result before applying it, so it can also cover
164 /// transport-delivered notes another sync path fetched in the same call.
165 pub async fn fetch_chain_updates(
166 &self,
167 state_sync: &StateSync,
168 ) -> Result<ChainSyncData, ClientError> {
169 let input = self.build_sync_input().await?;
170 let block_from = block_num_from_forest(&self.get_current_partial_mmr().await?)?;
171
172 let mut chain_sync_data = state_sync.fetch_state(block_from, input).await?;
173 self.collect_account_witnesses(&mut chain_sync_data).await?;
174
175 Ok(chain_sync_data)
176 }
177
178 /// Builds the [`StateSync`] driving one chain sync.
179 ///
180 /// Each `NoteObserver` owns its own per-sync state, so this must be called once per sync rather
181 /// than shared; `with_note_observer` just attaches it.
182 async fn state_sync(&self) -> Result<StateSync, ClientError> {
183 let validator_config = self.get_validator_config().await?;
184
185 Ok(StateSync::new(
186 self.rpc_api.clone(),
187 Arc::new(self.note_screener()),
188 self.tx_discard_delta,
189 validator_config,
190 )
191 .with_note_observer(Arc::new(PswapChainObserver::new(self.store.clone()))))
192 }
193
194 /// Verifies fetched chain data against the client's partial MMR and saves the resulting update
195 /// to the store.
196 ///
197 /// [`StateSync::derive_state_updates`] and [`StateSync::fetch_nullifiers`] must have run on the
198 /// data first. Also caches the partial MMR and prunes irrelevant blocks.
199 ///
200 /// # Errors
201 ///
202 /// Returns an error if the client no longer starts where the data was fetched from, which means
203 /// another sync advanced the store in between and the data is stale.
204 pub async fn apply_chain_updates(
205 &mut self,
206 state_sync: &StateSync,
207 chain_sync_data: ChainSyncData,
208 ) -> Result<SyncSummary, ClientError> {
209 let mut partial_mmr = self.get_current_partial_mmr().await?;
210
211 let block_from = block_num_from_forest(&partial_mmr)?;
212 if block_from != chain_sync_data.block_from {
213 return Err(ClientError::ChainValidationError(format!(
214 "chain sync chain_sync_data starts at block {} but the client is at block {block_from}",
215 chain_sync_data.block_from
216 )));
217 }
218
219 let state_sync_update = StateSync::build_update(chain_sync_data, &mut partial_mmr)?;
220
221 let sync_summary: SyncSummary = (&state_sync_update).into();
222 // Post-sync observer hooks; run before persisting. Per-observer errors are logged, not
223 // propagated.
224 state_sync.run_apply_hooks(&state_sync_update).await?;
225
226 info!("Applying changes to the store.");
227
228 // Apply received and computed updates to the store
229 self.store
230 .apply_state_sync(state_sync_update)
231 .await
232 .map_err(ClientError::StoreError)?;
233 // Cache MMR so pruning can reuse in-memory MMR.
234 self.cache_partial_mmr(partial_mmr).await?;
235
236 self.maybe_untrack_and_prune_irrelevant_blocks().await?;
237
238 Ok(sync_summary)
239 }
240
241 /// Fetches private notes from the Note Transport Layer for the tracked note tags.
242 ///
243 /// Returns the IDs of notes imported in this call. No-op (returns an empty vec) if note
244 /// transport is disabled. A failed request returns an error after successful pages and their
245 /// cursors are saved.
246 pub async fn sync_note_transport(&mut self) -> Result<Vec<NoteId>, ClientError> {
247 if !self.is_note_transport_enabled() {
248 return Ok(Vec::new());
249 }
250 self.ensure_genesis_in_place().await?;
251
252 let mut note_transport_update = self.fetch_note_transport_updates().await?;
253 let fetch_error = note_transport_update.fetch_error.take();
254 let (imported_ids, _) = self.apply_note_transport_update(note_transport_update).await?;
255 if let Some(error) = fetch_error {
256 return Err(error);
257 }
258 Ok(imported_ids)
259 }
260
261 /// Runs the full client sync: private notes from the Note Transport Layer and the client's
262 /// on-chain state with the Miden node.
263 ///
264 /// The NTL and the node are fetched concurrently, and everything that writes runs sequentially
265 /// afterwards:
266 ///
267 /// 1. Concurrently: the note transport fetch and [`Client::fetch_chain_updates`]. Only node and
268 /// NTL calls happen here, which is all that benefits from overlapping.
269 /// 2. The transport writes, when its fetch succeeded, whose records are then tracked in the
270 /// chain sync's note updates.
271 /// 3. [`StateSync::derive_state_updates`], which screens the node's notes against the store —
272 /// hence after step 2, so a transport-delivered note is recognised rather than discarded —
273 /// and applies a commitment reported this sync to those records.
274 /// 4. [`StateSync::fetch_nullifiers`], covering the tracked notes *and* the transport-delivered
275 /// ones, so a note delivered and consumed in the same window is reported as consumed by this
276 /// call.
277 /// 5. The chain update, written last: a nullified transport-delivered note is saved as an
278 /// update to the row step 2 inserts.
279 ///
280 /// A transport failure is logged and the chain sync continues. Successful transport pages are
281 /// imported and their cursors are saved. Failed requests keep their previous positions for
282 /// retry. The relay outbox is persisted during the fetch and retried on the next sync.
283 pub async fn sync_state(&mut self) -> Result<SyncSummary, ClientError> {
284 // Both fetch phases need genesis in place, and connecting here means the two concurrent
285 // futures never race on the RPC client's lazy connect.
286 self.ensure_genesis_in_place().await?;
287 self.ensure_rpc_limits_in_place().await?;
288
289 let state_sync = self.state_sync().await?;
290 let (note_transport_update, chain_sync_data) = futures::join!(
291 self.fetch_note_transport_updates(),
292 self.fetch_chain_updates(&state_sync),
293 );
294
295 // An NTL failure does not end the sync
296 let (new_private_notes, imported_notes) = match note_transport_update {
297 Ok(note_transport_update) => {
298 self.apply_note_transport_update(note_transport_update).await?
299 },
300 Err(err) => {
301 warn!(?err, "note transport fetch failed; syncing the chain without it");
302 (Vec::new(), Vec::new())
303 },
304 };
305
306 let mut chain_sync_data = chain_sync_data?;
307
308 // The chain sync built its note updates from a store snapshot taken before the import, so
309 // the imported records are added here. Without them this sync has no record to apply its
310 // verdicts to, and a note committed within this sync's own block range stays expected.
311 let imported_notes =
312 self.get_input_notes(NoteFilter::DetailsCommitments(imported_notes)).await?;
313 chain_sync_data.note_updates.track_existing_input_notes(imported_notes);
314
315 state_sync.derive_state_updates(&mut chain_sync_data).await?;
316 state_sync.fetch_nullifiers(&mut chain_sync_data).await?;
317
318 let mut summary = self.apply_chain_updates(&state_sync, chain_sync_data).await?;
319 summary.new_private_notes = new_private_notes;
320 Ok(summary)
321 }
322
323 /// Builds a default [`StateSyncInput`] from the current client state.
324 ///
325 /// This includes all tracked account headers, all unique note tags, all unspent input and
326 /// output notes, and all uncommitted transactions.
327 pub async fn build_sync_input(&self) -> Result<StateSyncInput, ClientError> {
328 let accounts = self
329 .store
330 .get_account_headers()
331 .await?
332 .into_iter()
333 .map(|(header, _status)| header)
334 .collect();
335
336 let note_tags = self.store.get_unique_note_tags().await?;
337
338 let input_notes = self.store.get_input_notes(NoteFilter::Unspent).await?;
339 let output_notes = self.store.get_output_notes(NoteFilter::Unspent).await?;
340
341 let uncommitted_transactions =
342 self.store.get_transactions(TransactionFilter::Uncommitted).await?;
343
344 Ok(StateSyncInput {
345 accounts,
346 note_tags,
347 input_notes,
348 output_notes,
349 uncommitted_transactions,
350 })
351 }
352
353 /// Applies the state sync update to the store and prunes irrelevant blocks according to the
354 /// configured cadence.
355 ///
356 /// See [`crate::Store::apply_state_sync()`] for what the update implies.
357 pub async fn apply_state_sync(&mut self, update: StateSyncUpdate) -> Result<(), ClientError> {
358 self.store.apply_state_sync(update).await?;
359
360 self.maybe_untrack_and_prune_irrelevant_blocks().await?;
361
362 Ok(())
363 }
364
365 /// Prunes irrelevant blocks and their MMR authentication nodes according to the configured
366 /// cadence.
367 async fn maybe_untrack_and_prune_irrelevant_blocks(&mut self) -> Result<(), ClientError> {
368 let Some(interval) = self.irrelevant_block_prune_interval else {
369 return Ok(());
370 };
371
372 let sync_height = self.store.get_sync_height().await?;
373
374 if let Some(last_prune_height) = self.last_irrelevant_block_prune_sync_height
375 && sync_height < last_prune_height + interval
376 {
377 return Ok(());
378 }
379
380 self.untrack_and_prune_irrelevant_blocks().await?;
381 self.last_irrelevant_block_prune_sync_height = Some(sync_height);
382
383 Ok(())
384 }
385
386 /// Prunes irrelevant block data from the store.
387 ///
388 /// Identifies tracked blocks whose input notes have all been consumed, untracks them from the
389 /// `PartialMmr` to determine which authentication nodes are no longer needed, then delegates to
390 /// [`Store::untrack_and_prune_irrelevant_blocks`] to atomically remove the stale nodes, mark
391 /// the blocks as irrelevant, and delete irrelevant block headers. Any caller of this function
392 /// should've cached the `PartialMmr` beforehand.
393 async fn untrack_and_prune_irrelevant_blocks(&mut self) -> Result<(), ClientError> {
394 let tracked_blocks = self.store.get_tracked_block_header_numbers().await?;
395 let to_untrack: Vec<usize> = if tracked_blocks.is_empty() {
396 // Do not early-return: even without blocks to untrack, old irrelevant tip headers may
397 // need pruning.
398 Vec::new()
399 } else {
400 // Blocks that still have at least one unspent note need to stay tracked.
401 let unspent_notes = self.store.get_input_notes(NoteFilter::Unspent).await?;
402 let live_blocks: BTreeSet<usize> = unspent_notes
403 .iter()
404 .filter_map(|n| n.inclusion_proof().map(|p| p.location().block_num().as_usize()))
405 .collect();
406
407 tracked_blocks.difference(&live_blocks).copied().collect()
408 };
409
410 let mut blocks_to_untrack = Vec::new();
411 let mut nodes_to_remove = Vec::new();
412 let mut updated_partial_mmr = None;
413
414 if !to_untrack.is_empty() {
415 // Rebuild the PartialMmr and untrack each block to collect the authentication node
416 // indices that are no longer needed by any remaining tracked leaf.
417 let mut partial_mmr = self.get_current_partial_mmr().await?;
418 nodes_to_remove = untrack_blocks(&mut partial_mmr, to_untrack.iter().copied());
419
420 blocks_to_untrack = to_untrack
421 .iter()
422 .map(|&b| BlockNumber::from(u32::try_from(b).expect("block number fits in u32")))
423 .collect();
424 updated_partial_mmr = Some(partial_mmr);
425 }
426
427 // Store deletes stale auth nodes, marks blocks as irrelevant, and removes irrelevant block
428 // headers. Old irrelevant tip headers may still need pruning.
429 self.store
430 .untrack_and_prune_irrelevant_blocks(&blocks_to_untrack, &nodes_to_remove)
431 .await?;
432
433 if let Some(partial_mmr) = updated_partial_mmr {
434 self.cache_partial_mmr(partial_mmr).await?;
435 }
436
437 Ok(())
438 }
439
440 /// Ensures that the RPC limits are set in the RPC client. If not already cached, fetches them
441 /// from the node and persists them in the store.
442 pub async fn ensure_rpc_limits_in_place(&mut self) -> Result<(), ClientError> {
443 if self.rpc_api.has_rpc_limits().is_some() {
444 return Ok(());
445 }
446
447 let limits = self.rpc_api.get_rpc_limits().await?;
448 self.store.set_rpc_limits(limits).await?;
449 Ok(())
450 }
451
452 // ACCOUNT WITNESS PREFETCHING
453 // --------------------------------------------------------------------------------------------
454
455 /// Adds to `chain_sync_data` the account witness of every registered account the sync did not
456 /// already fetch one for, so that all of them are stored with the rest of the update.
457 ///
458 /// Every account needs a witness at the new chain tip, whether or not its own state changed,
459 /// since a witness breaks when any other account in the tree moves.
460 ///
461 /// # Errors
462 ///
463 /// Fails if any witness cannot be fetched or validated. A successful sync therefore leaves a
464 /// witness at the sync height for every registered account.
465 async fn collect_account_witnesses(
466 &self,
467 chain_sync_data: &mut ChainSyncData,
468 ) -> Result<(), ClientError> {
469 let account_ids = self.store.tracked_account_witnesses().await?;
470 if account_ids.is_empty() {
471 return Ok(());
472 }
473
474 // The header of the block the witnesses must open under. The sync only carries it when it
475 // advanced; otherwise the client is already at that block and the store holds its header.
476 let chain_tip_header = match chain_sync_data.chain_tip_header() {
477 Some(header) => header.clone(),
478 None => self.get_latest_block_header().await?,
479 };
480
481 let already_fetched: BTreeSet<AccountId> = chain_sync_data
482 .account_updates
483 .account_witnesses()
484 .iter()
485 .map(|(account_id, _)| *account_id)
486 .collect();
487
488 let mut to_fetch = Vec::new();
489 for account_id in account_ids {
490 if already_fetched.contains(&account_id) {
491 continue;
492 }
493 // The chain did not advance, so the client is already synced to the chain tip. A stored
494 // witness is therefore already the witness for this block.
495 if chain_sync_data.chain_tip_header().is_none()
496 && self.store.get_account_witness(account_id).await?.is_some()
497 {
498 continue;
499 }
500 to_fetch.push(account_id);
501 }
502
503 // Bounded fan-out, under the same limit the sync uses for its own `get_account` requests.
504 let header = &chain_tip_header;
505 let witnesses: Vec<(AccountId, AccountWitness)> = futures::stream::iter(to_fetch)
506 .map(|account_id| async move {
507 let witness = self.fetch_account_witness(account_id, header).await?;
508 Ok::<_, ClientError>((account_id, witness))
509 })
510 .buffered(MAX_CONCURRENT_ACCOUNT_FETCHES)
511 .try_collect()
512 .await?;
513
514 chain_sync_data
515 .account_updates
516 .extend(AccountUpdates::default().with_account_witnesses(witnesses));
517
518 Ok(())
519 }
520
521 /// Fetches a single account's witness at `chain_tip_header`'s block.
522 async fn fetch_account_witness(
523 &self,
524 account_id: AccountId,
525 chain_tip_header: &BlockHeader,
526 ) -> Result<AccountWitness, ClientError> {
527 let chain_tip = chain_tip_header.block_num();
528
529 // The minimal request: no vault, no storage map entries, only the witness is wanted.
530 let (proof_block_num, proof) = self
531 .rpc_api
532 .get_account(account_id, GetAccountRequest::new().at(AccountStateAt::Block(chain_tip)))
533 .await?;
534
535 if proof_block_num != chain_tip {
536 return Err(ClientError::ChainValidationError(format!(
537 "get_account returned a proof at block {proof_block_num}, expected {chain_tip}"
538 )));
539 }
540
541 let (witness, _) = proof.into_parts();
542 validate_account_witness(&witness, account_id, chain_tip_header)?;
543
544 Ok(witness)
545 }
546}
547
548// SYNC SUMMARY
549// ================================================================================================
550
551/// Contains stats about the sync operation.
552#[derive(Debug, PartialEq)]
553pub struct SyncSummary {
554 /// Block number up to which the client has been synced.
555 pub block_num: BlockNumber,
556 /// IDs of new public notes that the client has received.
557 pub new_public_notes: Vec<NoteId>,
558 /// IDs of private notes imported from the Note Transport Layer in this sync. They are still
559 /// `Expected` until observed on-chain.
560 ///
561 /// Only populated by [`Client::sync_state`]; [`Client::sync_chain`] always leaves this empty
562 /// because it does not touch the Note Transport Layer.
563 pub new_private_notes: Vec<NoteId>,
564 /// IDs of tracked notes that have been committed.
565 pub committed_notes: Vec<NoteId>,
566 /// IDs of notes that have been consumed.
567 pub consumed_notes: Vec<NoteId>,
568 /// IDs of on-chain accounts that have been updated.
569 pub updated_accounts: Vec<AccountId>,
570 /// IDs of private accounts that have been locked.
571 pub locked_accounts: Vec<AccountId>,
572 /// IDs of committed transactions.
573 pub committed_transactions: Vec<TransactionId>,
574}
575
576impl SyncSummary {
577 pub fn new(
578 block_num: BlockNumber,
579 new_public_notes: Vec<NoteId>,
580 new_private_notes: Vec<NoteId>,
581 committed_notes: Vec<NoteId>,
582 consumed_notes: Vec<NoteId>,
583 updated_accounts: Vec<AccountId>,
584 locked_accounts: Vec<AccountId>,
585 committed_transactions: Vec<TransactionId>,
586 ) -> Self {
587 Self {
588 block_num,
589 new_public_notes,
590 new_private_notes,
591 committed_notes,
592 consumed_notes,
593 updated_accounts,
594 locked_accounts,
595 committed_transactions,
596 }
597 }
598
599 pub fn new_empty(block_num: BlockNumber) -> Self {
600 Self {
601 block_num,
602 new_public_notes: vec![],
603 new_private_notes: vec![],
604 committed_notes: vec![],
605 consumed_notes: vec![],
606 updated_accounts: vec![],
607 locked_accounts: vec![],
608 committed_transactions: vec![],
609 }
610 }
611
612 pub fn is_empty(&self) -> bool {
613 self.new_public_notes.is_empty()
614 && self.new_private_notes.is_empty()
615 && self.committed_notes.is_empty()
616 && self.consumed_notes.is_empty()
617 && self.updated_accounts.is_empty()
618 && self.locked_accounts.is_empty()
619 && self.committed_transactions.is_empty()
620 }
621
622 pub fn combine_with(&mut self, mut other: Self) {
623 self.block_num = max(self.block_num, other.block_num);
624 self.new_public_notes.append(&mut other.new_public_notes);
625 self.new_private_notes.append(&mut other.new_private_notes);
626 self.committed_notes.append(&mut other.committed_notes);
627 self.consumed_notes.append(&mut other.consumed_notes);
628 self.updated_accounts.append(&mut other.updated_accounts);
629 self.locked_accounts.append(&mut other.locked_accounts);
630 self.committed_transactions.append(&mut other.committed_transactions);
631 }
632}
633
634impl Serializable for SyncSummary {
635 fn write_into<W: miden_tx::utils::serde::ByteWriter>(&self, target: &mut W) {
636 self.block_num.write_into(target);
637 self.new_public_notes.write_into(target);
638 self.new_private_notes.write_into(target);
639 self.committed_notes.write_into(target);
640 self.consumed_notes.write_into(target);
641 self.updated_accounts.write_into(target);
642 self.locked_accounts.write_into(target);
643 self.committed_transactions.write_into(target);
644 }
645}
646
647impl Deserializable for SyncSummary {
648 fn read_from<R: miden_tx::utils::serde::ByteReader>(
649 source: &mut R,
650 ) -> Result<Self, DeserializationError> {
651 let block_num = BlockNumber::read_from(source)?;
652 let new_public_notes = Vec::<NoteId>::read_from(source)?;
653 let new_private_notes = Vec::<NoteId>::read_from(source)?;
654 let committed_notes = Vec::<NoteId>::read_from(source)?;
655 let consumed_notes = Vec::<NoteId>::read_from(source)?;
656 let updated_accounts = Vec::<AccountId>::read_from(source)?;
657 let locked_accounts = Vec::<AccountId>::read_from(source)?;
658 let committed_transactions = Vec::<TransactionId>::read_from(source)?;
659
660 Ok(Self {
661 block_num,
662 new_public_notes,
663 new_private_notes,
664 committed_notes,
665 consumed_notes,
666 updated_accounts,
667 locked_accounts,
668 committed_transactions,
669 })
670 }
671}