miden_client/sync/
state_sync_update.rs1use alloc::collections::{BTreeMap, BTreeSet};
2use alloc::vec::Vec;
3
4use miden_objects::Word;
5use miden_objects::account::AccountId;
6use miden_objects::block::{BlockHeader, BlockNumber};
7use miden_objects::crypto::merkle::{InOrderIndex, MmrPeaks};
8use miden_objects::note::{NoteId, Nullifier};
9use miden_objects::transaction::TransactionId;
10
11use super::SyncSummary;
12use crate::account::Account;
13use crate::note::{NoteUpdateTracker, NoteUpdateType};
14use crate::rpc::domain::transaction::TransactionInclusion;
15use crate::transaction::{DiscardCause, TransactionRecord, TransactionStatus};
16
17#[derive(Default)]
22pub struct StateSyncUpdate {
23 pub block_num: BlockNumber,
25 pub block_updates: BlockUpdates,
27 pub note_updates: NoteUpdateTracker,
29 pub transaction_updates: TransactionUpdateTracker,
31 pub account_updates: AccountUpdates,
33}
34
35impl From<&StateSyncUpdate> for SyncSummary {
36 fn from(value: &StateSyncUpdate) -> Self {
37 let new_public_note_ids = value
38 .note_updates
39 .updated_input_notes()
40 .filter_map(|note_update| {
41 let note = note_update.inner();
42 if let NoteUpdateType::Insert = note_update.update_type() {
43 Some(note.id())
44 } else {
45 None
46 }
47 })
48 .collect();
49
50 let committed_note_ids: BTreeSet<NoteId> = value
51 .note_updates
52 .updated_input_notes()
53 .filter_map(|note_update| {
54 let note = note_update.inner();
55 if let NoteUpdateType::Update = note_update.update_type() {
56 note.is_committed().then_some(note.id())
57 } else {
58 None
59 }
60 })
61 .chain(value.note_updates.updated_output_notes().filter_map(|note_update| {
62 let note = note_update.inner();
63 if let NoteUpdateType::Update = note_update.update_type() {
64 note.is_committed().then_some(note.id())
65 } else {
66 None
67 }
68 }))
69 .collect();
70
71 let consumed_note_ids: BTreeSet<NoteId> = value
72 .note_updates
73 .updated_input_notes()
74 .filter_map(|note| note.inner().is_consumed().then_some(note.inner().id()))
75 .collect();
76
77 SyncSummary::new(
78 value.block_num,
79 new_public_note_ids,
80 committed_note_ids.into_iter().collect(),
81 consumed_note_ids.into_iter().collect(),
82 value
83 .account_updates
84 .updated_public_accounts()
85 .iter()
86 .map(Account::id)
87 .collect(),
88 value
89 .account_updates
90 .mismatched_private_accounts()
91 .iter()
92 .map(|(id, _)| *id)
93 .collect(),
94 value.transaction_updates.committed_transactions().map(|t| t.id).collect(),
95 )
96 }
97}
98
99#[derive(Debug, Clone, Default)]
101pub struct BlockUpdates {
102 block_headers: Vec<(BlockHeader, bool, MmrPeaks)>,
105 new_authentication_nodes: Vec<(InOrderIndex, Word)>,
108}
109
110impl BlockUpdates {
111 pub fn new(
113 block_headers: Vec<(BlockHeader, bool, MmrPeaks)>,
114 new_authentication_nodes: Vec<(InOrderIndex, Word)>,
115 ) -> Self {
116 Self { block_headers, new_authentication_nodes }
117 }
118
119 pub fn block_headers(&self) -> &[(BlockHeader, bool, MmrPeaks)] {
122 &self.block_headers
123 }
124
125 pub fn new_authentication_nodes(&self) -> &[(InOrderIndex, Word)] {
128 &self.new_authentication_nodes
129 }
130
131 pub(crate) fn extend(&mut self, other: BlockUpdates) {
133 self.block_headers.extend(other.block_headers);
134 self.new_authentication_nodes.extend(other.new_authentication_nodes);
135 }
136}
137
138#[derive(Default)]
140pub struct TransactionUpdateTracker {
141 transactions: BTreeMap<TransactionId, TransactionRecord>,
142}
143
144impl TransactionUpdateTracker {
145 pub fn new(transactions: Vec<TransactionRecord>) -> Self {
147 let transactions =
148 transactions.into_iter().map(|tx| (tx.id, tx)).collect::<BTreeMap<_, _>>();
149
150 Self { transactions }
151 }
152
153 pub fn committed_transactions(&self) -> impl Iterator<Item = &TransactionRecord> {
155 self.transactions
156 .values()
157 .filter(|tx| matches!(tx.status, TransactionStatus::Committed { .. }))
158 }
159
160 pub fn discarded_transactions(&self) -> impl Iterator<Item = &TransactionRecord> {
162 self.transactions
163 .values()
164 .filter(|tx| matches!(tx.status, TransactionStatus::Discarded(_)))
165 }
166
167 fn mutable_pending_transactions(&mut self) -> impl Iterator<Item = &mut TransactionRecord> {
169 self.transactions
170 .values_mut()
171 .filter(|tx| matches!(tx.status, TransactionStatus::Pending))
172 }
173
174 pub fn updated_transaction_ids(&self) -> impl Iterator<Item = TransactionId> {
176 self.committed_transactions()
177 .chain(self.discarded_transactions())
178 .map(|tx| tx.id)
179 }
180
181 pub fn apply_transaction_inclusion(
184 &mut self,
185 transaction_inclusion: &TransactionInclusion,
186 timestamp: u64,
187 ) {
188 if let Some(transaction) = self.transactions.get_mut(&transaction_inclusion.transaction_id)
189 {
190 transaction.commit_transaction(transaction_inclusion.block_num.into(), timestamp);
191 }
192 }
193
194 pub fn apply_sync_height_update(
197 &mut self,
198 new_sync_height: BlockNumber,
199 tx_graceful_blocks: Option<u32>,
200 ) {
201 if let Some(tx_graceful_blocks) = tx_graceful_blocks {
202 self.discard_transaction_with_predicate(
203 |transaction| {
204 transaction.details.submission_height
205 < new_sync_height.checked_sub(tx_graceful_blocks).unwrap_or_default()
206 },
207 DiscardCause::Stale,
208 );
209 }
210
211 self.discard_transaction_with_predicate(
212 |transaction| transaction.details.expiration_block_num <= new_sync_height,
213 DiscardCause::Expired,
214 );
215 }
216
217 pub fn apply_input_note_nullified(&mut self, input_note_nullifier: Nullifier) {
221 self.discard_transaction_with_predicate(
222 |transaction| {
223 transaction
226 .details
227 .input_note_nullifiers
228 .contains(&input_note_nullifier.as_word())
229 },
230 DiscardCause::InputConsumed,
231 );
232 }
233
234 pub fn apply_invalid_initial_account_state(&mut self, invalid_account_state: Word) {
236 self.discard_transaction_with_predicate(
237 |transaction| transaction.details.init_account_state == invalid_account_state,
238 DiscardCause::DiscardedInitialState,
239 );
240 }
241
242 fn discard_transaction_with_predicate<F>(&mut self, predicate: F, discard_cause: DiscardCause)
245 where
246 F: Fn(&TransactionRecord) -> bool,
247 {
248 let mut new_invalid_account_states = vec![];
249
250 for transaction in self.mutable_pending_transactions() {
251 if predicate(transaction) {
252 transaction.discard_transaction(discard_cause);
253 new_invalid_account_states.push(transaction.details.final_account_state);
254 }
255 }
256
257 for state in new_invalid_account_states {
258 self.apply_invalid_initial_account_state(state);
259 }
260 }
261}
262
263#[derive(Debug, Clone, Default)]
268pub struct AccountUpdates {
269 updated_public_accounts: Vec<Account>,
271 mismatched_private_accounts: Vec<(AccountId, Word)>,
278}
279
280impl AccountUpdates {
281 pub fn new(
283 updated_public_accounts: Vec<Account>,
284 mismatched_private_accounts: Vec<(AccountId, Word)>,
285 ) -> Self {
286 Self {
287 updated_public_accounts,
288 mismatched_private_accounts,
289 }
290 }
291
292 pub fn updated_public_accounts(&self) -> &[Account] {
294 &self.updated_public_accounts
295 }
296
297 pub fn mismatched_private_accounts(&self) -> &[(AccountId, Word)] {
299 &self.mismatched_private_accounts
300 }
301
302 pub fn extend(&mut self, other: AccountUpdates) {
303 self.updated_public_accounts.extend(other.updated_public_accounts);
304 self.mismatched_private_accounts.extend(other.mismatched_private_accounts);
305 }
306}