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