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::{debug, 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 debug!(sync_summary = ?sync_summary, "Sync summary computed");
223
224 // Post-sync observer hooks; run before persisting. Per-observer errors are logged, not
225 // propagated.
226 state_sync.run_apply_hooks(&state_sync_update).await?;
227
228 info!("Applying changes to the store.");
229
230 // Apply received and computed updates to the store
231 self.store
232 .apply_state_sync(state_sync_update)
233 .await
234 .map_err(ClientError::StoreError)?;
235
236 // Cache MMR so pruning can reuse in-memory MMR.
237 self.cache_partial_mmr(partial_mmr).await?;
238
239 self.maybe_untrack_and_prune_irrelevant_blocks().await?;
240
241 Ok(sync_summary)
242 }
243
244 /// Fetches private notes from the Note Transport Layer for the tracked note tags.
245 ///
246 /// Returns the IDs of notes imported in this call. No-op (returns an empty vec) if note
247 /// transport is disabled.
248 pub async fn sync_note_transport(&mut self) -> Result<Vec<NoteId>, ClientError> {
249 if !self.is_note_transport_enabled() {
250 return Ok(Vec::new());
251 }
252 self.ensure_genesis_in_place().await?;
253
254 let note_transport_update = self.fetch_note_transport_updates().await?;
255 let (imported_ids, _) = self.apply_note_transport_update(note_transport_update).await?;
256 Ok(imported_ids)
257 }
258
259 /// Runs the full client sync: private notes from the Note Transport Layer and the client's
260 /// on-chain state with the Miden node.
261 ///
262 /// The NTL and the node are fetched concurrently, and everything that writes runs sequentially
263 /// afterwards:
264 ///
265 /// 1. Concurrently: the note transport fetch and [`Client::fetch_chain_updates`]. Only node and
266 /// NTL calls happen here, which is all that benefits from overlapping.
267 /// 2. The transport writes, when its fetch succeeded, whose records are then tracked in the
268 /// chain sync's note updates.
269 /// 3. [`StateSync::derive_state_updates`], which screens the node's notes against the store —
270 /// hence after step 2, so a transport-delivered note is recognised rather than discarded —
271 /// and applies a commitment reported this sync to those records.
272 /// 4. [`StateSync::fetch_nullifiers`], covering the tracked notes *and* the transport-delivered
273 /// ones, so a note delivered and consumed in the same window is reported as consumed by this
274 /// call.
275 /// 5. The chain update, written last: a nullified transport-delivered note is saved as an
276 /// update to the row step 2 inserts.
277 ///
278 /// A transport failure is logged and the chain sync continues without it, leaving the transport
279 /// cursor for the next call to retry. Before step 2 but the relay outbox, which
280 /// [`Client::flush_relay_outbox`] persists during the fetch and the next sync retries.
281 pub async fn sync_state(&mut self) -> Result<SyncSummary, ClientError> {
282 // Both fetch phases need genesis in place, and connecting here means the two concurrent
283 // futures never race on the RPC client's lazy connect.
284 self.ensure_genesis_in_place().await?;
285 self.ensure_rpc_limits_in_place().await?;
286
287 let state_sync = self.state_sync().await?;
288 let (note_transport_update, chain_sync_data) = futures::join!(
289 self.fetch_note_transport_updates(),
290 self.fetch_chain_updates(&state_sync),
291 );
292
293 // An NTL failure does not end the sync
294 let (new_private_notes, imported_notes) = match note_transport_update {
295 Ok(note_transport_update) => {
296 self.apply_note_transport_update(note_transport_update).await?
297 },
298 Err(err) => {
299 warn!(?err, "note transport fetch failed; syncing the chain without it");
300 (Vec::new(), Vec::new())
301 },
302 };
303
304 let mut chain_sync_data = chain_sync_data?;
305
306 // The chain sync built its note updates from a store snapshot taken before the import, so
307 // the imported records are added here. Without them this sync has no record to apply its
308 // verdicts to, and a note committed within this sync's own block range stays expected.
309 let imported_notes =
310 self.get_input_notes(NoteFilter::DetailsCommitments(imported_notes)).await?;
311 chain_sync_data.note_updates.track_existing_input_notes(imported_notes);
312
313 state_sync.derive_state_updates(&mut chain_sync_data).await?;
314 state_sync.fetch_nullifiers(&mut chain_sync_data).await?;
315
316 let mut summary = self.apply_chain_updates(&state_sync, chain_sync_data).await?;
317 summary.new_private_notes = new_private_notes;
318 Ok(summary)
319 }
320
321 /// Builds a default [`StateSyncInput`] from the current client state.
322 ///
323 /// This includes all tracked account headers, all unique note tags, all unspent input and
324 /// output notes, and all uncommitted transactions.
325 pub async fn build_sync_input(&self) -> Result<StateSyncInput, ClientError> {
326 let accounts = self
327 .store
328 .get_account_headers()
329 .await?
330 .into_iter()
331 .map(|(header, _status)| header)
332 .collect();
333
334 let note_tags = self.store.get_unique_note_tags().await?;
335
336 let input_notes = self.store.get_input_notes(NoteFilter::Unspent).await?;
337 let output_notes = self.store.get_output_notes(NoteFilter::Unspent).await?;
338
339 let uncommitted_transactions =
340 self.store.get_transactions(TransactionFilter::Uncommitted).await?;
341
342 Ok(StateSyncInput {
343 accounts,
344 note_tags,
345 input_notes,
346 output_notes,
347 uncommitted_transactions,
348 })
349 }
350
351 /// Applies the state sync update to the store and prunes irrelevant blocks according to the
352 /// configured cadence.
353 ///
354 /// See [`crate::Store::apply_state_sync()`] for what the update implies.
355 pub async fn apply_state_sync(&mut self, update: StateSyncUpdate) -> Result<(), ClientError> {
356 self.store.apply_state_sync(update).await?;
357
358 self.maybe_untrack_and_prune_irrelevant_blocks().await?;
359
360 Ok(())
361 }
362
363 /// Prunes irrelevant blocks and their MMR authentication nodes according to the configured
364 /// cadence.
365 async fn maybe_untrack_and_prune_irrelevant_blocks(&mut self) -> Result<(), ClientError> {
366 let Some(interval) = self.irrelevant_block_prune_interval else {
367 return Ok(());
368 };
369
370 let sync_height = self.store.get_sync_height().await?;
371
372 if let Some(last_prune_height) = self.last_irrelevant_block_prune_sync_height
373 && sync_height < last_prune_height + interval
374 {
375 return Ok(());
376 }
377
378 self.untrack_and_prune_irrelevant_blocks().await?;
379 self.last_irrelevant_block_prune_sync_height = Some(sync_height);
380
381 Ok(())
382 }
383
384 /// Prunes irrelevant block data from the store.
385 ///
386 /// Identifies tracked blocks whose input notes have all been consumed, untracks them from the
387 /// `PartialMmr` to determine which authentication nodes are no longer needed, then delegates to
388 /// [`Store::untrack_and_prune_irrelevant_blocks`] to atomically remove the stale nodes, mark
389 /// the blocks as irrelevant, and delete irrelevant block headers. Any caller of this function
390 /// should've cached the `PartialMmr` beforehand.
391 async fn untrack_and_prune_irrelevant_blocks(&mut self) -> Result<(), ClientError> {
392 let tracked_blocks = self.store.get_tracked_block_header_numbers().await?;
393 let to_untrack: Vec<usize> = if tracked_blocks.is_empty() {
394 // Do not early-return: even without blocks to untrack, old irrelevant tip headers may
395 // need pruning.
396 Vec::new()
397 } else {
398 // Blocks that still have at least one unspent note need to stay tracked.
399 let unspent_notes = self.store.get_input_notes(NoteFilter::Unspent).await?;
400 let live_blocks: BTreeSet<usize> = unspent_notes
401 .iter()
402 .filter_map(|n| n.inclusion_proof().map(|p| p.location().block_num().as_usize()))
403 .collect();
404
405 tracked_blocks.difference(&live_blocks).copied().collect()
406 };
407
408 let mut blocks_to_untrack = Vec::new();
409 let mut nodes_to_remove = Vec::new();
410 let mut updated_partial_mmr = None;
411
412 if !to_untrack.is_empty() {
413 // Rebuild the PartialMmr and untrack each block to collect the authentication node
414 // indices that are no longer needed by any remaining tracked leaf.
415 let mut partial_mmr = self.get_current_partial_mmr().await?;
416 nodes_to_remove = untrack_blocks(&mut partial_mmr, to_untrack.iter().copied());
417
418 blocks_to_untrack = to_untrack
419 .iter()
420 .map(|&b| BlockNumber::from(u32::try_from(b).expect("block number fits in u32")))
421 .collect();
422 updated_partial_mmr = Some(partial_mmr);
423 }
424
425 // Store deletes stale auth nodes, marks blocks as irrelevant, and removes irrelevant block
426 // headers. Old irrelevant tip headers may still need pruning.
427 self.store
428 .untrack_and_prune_irrelevant_blocks(&blocks_to_untrack, &nodes_to_remove)
429 .await?;
430
431 if let Some(partial_mmr) = updated_partial_mmr {
432 self.cache_partial_mmr(partial_mmr).await?;
433 }
434
435 Ok(())
436 }
437
438 /// Ensures that the RPC limits are set in the RPC client. If not already cached, fetches them
439 /// from the node and persists them in the store.
440 pub async fn ensure_rpc_limits_in_place(&mut self) -> Result<(), ClientError> {
441 if self.rpc_api.has_rpc_limits().is_some() {
442 return Ok(());
443 }
444
445 let limits = self.rpc_api.get_rpc_limits().await?;
446 self.store.set_rpc_limits(limits).await?;
447 Ok(())
448 }
449
450 // ACCOUNT WITNESS PREFETCHING
451 // --------------------------------------------------------------------------------------------
452
453 /// Adds to `chain_sync_data` the account witness of every registered account the sync did not
454 /// already fetch one for, so that all of them are stored with the rest of the update.
455 ///
456 /// Every account needs a witness at the new chain tip, whether or not its own state changed,
457 /// since a witness breaks when any other account in the tree moves.
458 ///
459 /// # Errors
460 ///
461 /// Fails if any witness cannot be fetched or validated. A successful sync therefore leaves a
462 /// witness at the sync height for every registered account.
463 async fn collect_account_witnesses(
464 &self,
465 chain_sync_data: &mut ChainSyncData,
466 ) -> Result<(), ClientError> {
467 let account_ids = self.store.tracked_account_witnesses().await?;
468 if account_ids.is_empty() {
469 return Ok(());
470 }
471
472 // The header of the block the witnesses must open under. The sync only carries it when it
473 // advanced; otherwise the client is already at that block and the store holds its header.
474 let chain_tip_header = match chain_sync_data.chain_tip_header() {
475 Some(header) => header.clone(),
476 None => self.get_latest_block_header().await?,
477 };
478
479 let already_fetched: BTreeSet<AccountId> = chain_sync_data
480 .account_updates
481 .account_witnesses()
482 .iter()
483 .map(|(account_id, _)| *account_id)
484 .collect();
485
486 let mut to_fetch = Vec::new();
487 for account_id in account_ids {
488 if already_fetched.contains(&account_id) {
489 continue;
490 }
491 // The chain did not advance, so the client is already synced to the chain tip. A stored
492 // witness is therefore already the witness for this block.
493 if chain_sync_data.chain_tip_header().is_none()
494 && self.store.get_account_witness(account_id).await?.is_some()
495 {
496 continue;
497 }
498 to_fetch.push(account_id);
499 }
500
501 // Bounded fan-out, under the same limit the sync uses for its own `get_account` requests.
502 let header = &chain_tip_header;
503 let witnesses: Vec<(AccountId, AccountWitness)> = futures::stream::iter(to_fetch)
504 .map(|account_id| async move {
505 let witness = self.fetch_account_witness(account_id, header).await?;
506 Ok::<_, ClientError>((account_id, witness))
507 })
508 .buffered(MAX_CONCURRENT_ACCOUNT_FETCHES)
509 .try_collect()
510 .await?;
511
512 chain_sync_data
513 .account_updates
514 .extend(AccountUpdates::default().with_account_witnesses(witnesses));
515
516 Ok(())
517 }
518
519 /// Fetches a single account's witness at `chain_tip_header`'s block.
520 async fn fetch_account_witness(
521 &self,
522 account_id: AccountId,
523 chain_tip_header: &BlockHeader,
524 ) -> Result<AccountWitness, ClientError> {
525 let chain_tip = chain_tip_header.block_num();
526
527 // The minimal request: no vault, no storage map entries, only the witness is wanted.
528 let (proof_block_num, proof) = self
529 .rpc_api
530 .get_account(account_id, GetAccountRequest::new().at(AccountStateAt::Block(chain_tip)))
531 .await?;
532
533 if proof_block_num != chain_tip {
534 return Err(ClientError::ChainValidationError(format!(
535 "get_account returned a proof at block {proof_block_num}, expected {chain_tip}"
536 )));
537 }
538
539 let (witness, _) = proof.into_parts();
540 validate_account_witness(&witness, account_id, chain_tip_header)?;
541
542 Ok(witness)
543 }
544}
545
546// SYNC SUMMARY
547// ================================================================================================
548
549/// Contains stats about the sync operation.
550#[derive(Debug, PartialEq)]
551pub struct SyncSummary {
552 /// Block number up to which the client has been synced.
553 pub block_num: BlockNumber,
554 /// IDs of new public notes that the client has received.
555 pub new_public_notes: Vec<NoteId>,
556 /// IDs of private notes imported from the Note Transport Layer in this sync. They are still
557 /// `Expected` until observed on-chain.
558 ///
559 /// Only populated by [`Client::sync_state`]; [`Client::sync_chain`] always leaves this empty
560 /// because it does not touch the Note Transport Layer.
561 pub new_private_notes: Vec<NoteId>,
562 /// IDs of tracked notes that have been committed.
563 pub committed_notes: Vec<NoteId>,
564 /// IDs of notes that have been consumed.
565 pub consumed_notes: Vec<NoteId>,
566 /// IDs of on-chain accounts that have been updated.
567 pub updated_accounts: Vec<AccountId>,
568 /// IDs of private accounts that have been locked.
569 pub locked_accounts: Vec<AccountId>,
570 /// IDs of committed transactions.
571 pub committed_transactions: Vec<TransactionId>,
572}
573
574impl SyncSummary {
575 pub fn new(
576 block_num: BlockNumber,
577 new_public_notes: Vec<NoteId>,
578 new_private_notes: Vec<NoteId>,
579 committed_notes: Vec<NoteId>,
580 consumed_notes: Vec<NoteId>,
581 updated_accounts: Vec<AccountId>,
582 locked_accounts: Vec<AccountId>,
583 committed_transactions: Vec<TransactionId>,
584 ) -> Self {
585 Self {
586 block_num,
587 new_public_notes,
588 new_private_notes,
589 committed_notes,
590 consumed_notes,
591 updated_accounts,
592 locked_accounts,
593 committed_transactions,
594 }
595 }
596
597 pub fn new_empty(block_num: BlockNumber) -> Self {
598 Self {
599 block_num,
600 new_public_notes: vec![],
601 new_private_notes: vec![],
602 committed_notes: vec![],
603 consumed_notes: vec![],
604 updated_accounts: vec![],
605 locked_accounts: vec![],
606 committed_transactions: vec![],
607 }
608 }
609
610 pub fn is_empty(&self) -> bool {
611 self.new_public_notes.is_empty()
612 && self.new_private_notes.is_empty()
613 && self.committed_notes.is_empty()
614 && self.consumed_notes.is_empty()
615 && self.updated_accounts.is_empty()
616 && self.locked_accounts.is_empty()
617 && self.committed_transactions.is_empty()
618 }
619
620 pub fn combine_with(&mut self, mut other: Self) {
621 self.block_num = max(self.block_num, other.block_num);
622 self.new_public_notes.append(&mut other.new_public_notes);
623 self.new_private_notes.append(&mut other.new_private_notes);
624 self.committed_notes.append(&mut other.committed_notes);
625 self.consumed_notes.append(&mut other.consumed_notes);
626 self.updated_accounts.append(&mut other.updated_accounts);
627 self.locked_accounts.append(&mut other.locked_accounts);
628 self.committed_transactions.append(&mut other.committed_transactions);
629 }
630}
631
632impl Serializable for SyncSummary {
633 fn write_into<W: miden_tx::utils::serde::ByteWriter>(&self, target: &mut W) {
634 self.block_num.write_into(target);
635 self.new_public_notes.write_into(target);
636 self.new_private_notes.write_into(target);
637 self.committed_notes.write_into(target);
638 self.consumed_notes.write_into(target);
639 self.updated_accounts.write_into(target);
640 self.locked_accounts.write_into(target);
641 self.committed_transactions.write_into(target);
642 }
643}
644
645impl Deserializable for SyncSummary {
646 fn read_from<R: miden_tx::utils::serde::ByteReader>(
647 source: &mut R,
648 ) -> Result<Self, DeserializationError> {
649 let block_num = BlockNumber::read_from(source)?;
650 let new_public_notes = Vec::<NoteId>::read_from(source)?;
651 let new_private_notes = Vec::<NoteId>::read_from(source)?;
652 let committed_notes = Vec::<NoteId>::read_from(source)?;
653 let consumed_notes = Vec::<NoteId>::read_from(source)?;
654 let updated_accounts = Vec::<AccountId>::read_from(source)?;
655 let locked_accounts = Vec::<AccountId>::read_from(source)?;
656 let committed_transactions = Vec::<TransactionId>::read_from(source)?;
657
658 Ok(Self {
659 block_num,
660 new_public_notes,
661 new_private_notes,
662 committed_notes,
663 consumed_notes,
664 updated_accounts,
665 locked_accounts,
666 committed_transactions,
667 })
668 }
669}