1use std::fmt::Display;
2use std::sync::Arc;
3
4use miden_node_proto::domain::proof_request::BlockProofRequest;
5use miden_node_utils::ErrorReport;
6use miden_node_utils::tracing::{miden_instrument, miden_span_record};
7use miden_protocol::Word;
8use miden_protocol::account::AccountUpdateDetails;
9use miden_protocol::batch::OrderedBatches;
10use miden_protocol::block::account_tree::AccountMutationSet;
11use miden_protocol::block::nullifier_tree::NullifierMutationSet;
12use miden_protocol::block::{BlockBody, BlockHeader, BlockInputs, BlockNumber, SignedBlock};
13use miden_protocol::note::{NoteDetails, Nullifier};
14use miden_protocol::transaction::OutputNote;
15use miden_protocol::utils::serde::Serializable;
16use tokio::sync::oneshot;
17use tracing::{Instrument, info_span};
18
19use crate::db::NoteRecord;
20use crate::errors::{ApplyBlockError, ApplyBlockWithProvingInputsError, InvalidBlockError};
21use crate::state::block_lifecycle::{BlockLifecycle, lifecycle_events_enabled};
22use crate::state::{BlockNotification, InnerState, State};
23use crate::{COMPONENT, HistoricalError, LOG_TARGET};
24
25impl State {
26 #[miden_instrument(
30 target = COMPONENT,
31 skip_all,
32 err,
33 )]
34 pub async fn apply_block_with_proving_inputs(
35 &self,
36 ordered_batches: OrderedBatches,
37 block_inputs: BlockInputs,
38 signed_block: SignedBlock,
39 ) -> Result<(), ApplyBlockWithProvingInputsError> {
40 let block_header = signed_block.header().clone();
41 let block_num = block_header.block_num();
42
43 let proving_inputs = BlockProofRequest {
44 tx_batches: ordered_batches,
45 block_header,
46 block_inputs,
47 };
48
49 self.save_proving_inputs(block_num, &proving_inputs)
50 .await
51 .map_err(ApplyBlockWithProvingInputsError::SaveProvingInputs)?;
52
53 self.apply_block(signed_block)
54 .await
55 .map_err(ApplyBlockWithProvingInputsError::ApplyBlock)
56 }
57
58 #[miden_instrument(
86 target = COMPONENT,
87 skip_all,
88 err,
89 )]
90 pub async fn apply_block(&self, signed_block: SignedBlock) -> Result<(), ApplyBlockError> {
91 let _lock = self.writer.try_lock().map_err(|_| ApplyBlockError::ConcurrentWrite)?;
92
93 let header = signed_block.header();
94 let body = signed_block.body();
95
96 let block_num = header.block_num();
97 let block_commitment = header.commitment();
98 let num_transactions = body.transactions().as_slice().len();
99
100 miden_span_record!(
101 block.number = %block_num,
102 block.commitment = %block_commitment,
103 block.transactions.count = num_transactions,
104 );
105
106 self.validate_block_header(header, body).await?;
107
108 let block_lifecycle =
109 lifecycle_events_enabled().then(|| BlockLifecycle::from_block_body(block_num, body));
110 let unresolved_note_nullifiers = block_lifecycle
111 .as_ref()
112 .map_or_else(Vec::new, BlockLifecycle::unresolved_note_nullifiers);
113
114 let signed_block_bytes = signed_block.to_bytes();
118 let cache_bytes = signed_block_bytes.clone();
120 let store = Arc::clone(&self.block_store);
121 let block_save_task = tokio::spawn(
122 async move { store.save_block(block_num, &signed_block_bytes).await }.in_current_span(),
123 );
124
125 let (
126 nullifier_tree_old_root,
127 nullifier_tree_update,
128 account_tree_old_root,
129 account_tree_update,
130 ) = self.compute_tree_mutations(header, body).await?;
131
132 let notes = Self::build_note_records(header, body)?;
133
134 let (allow_acquire, acquired_allowed) = oneshot::channel::<()>();
136 let (inform_acquire_done, acquire_done) = oneshot::channel::<()>();
138
139 let account_patches =
142 Vec::from_iter(body.updated_accounts().iter().filter_map(
143 |update| match update.details() {
144 AccountUpdateDetails::Public(patch) => Some(patch.clone()),
145 AccountUpdateDetails::Private => None,
146 },
147 ));
148 let account_forest_update = self.with_forest_read_blocking(|forest| {
149 forest
150 .compute_block_update_mutations(block_num, account_patches)
151 .map_err(ApplyBlockError::AccountStateForestPreparation)
152 })?;
153 let precomputed_public_states = account_forest_update.account_states.clone();
154
155 let db = Arc::clone(&self.db);
159 let db_update_task = tokio::spawn(
160 async move {
161 db.apply_block(
162 allow_acquire,
163 acquire_done,
164 signed_block,
165 notes,
166 precomputed_public_states,
167 unresolved_note_nullifiers,
168 )
169 .await
170 }
171 .in_current_span(),
172 );
173
174 acquired_allowed
176 .instrument(info_span!(target: COMPONENT, "await_db_readiness"))
177 .await
178 .map_err(ApplyBlockError::ClosedChannel)?;
179
180 block_save_task.await??;
182
183 let resolved_note_ids = self.with_inner_and_forest_write_blocking(|inner, forest| {
184 if inner.nullifier_tree.root() != nullifier_tree_old_root
189 || inner.account_tree.root_latest() != account_tree_old_root
190 {
191 return Err(ApplyBlockError::ConcurrentWrite);
192 }
193
194 inform_acquire_done
197 .send(())
198 .map_err(|_| ApplyBlockError::DbUpdateTaskFailed("Receiver was dropped".into()))?;
199
200 let resolved_note_ids = tokio::runtime::Handle::current()
204 .block_on(db_update_task)?
205 .map_err(|err| ApplyBlockError::DbUpdateTaskFailed(err.as_report()))?;
206
207 let InnerState { nullifier_tree, blockchain, account_tree } = inner;
210 forest
211 .apply_precomputed_block_update(block_num, account_forest_update)
212 .unwrap_or_else(|error| {
213 Self::abort_after_post_commit_failure("account-state forest", &error)
214 });
215 nullifier_tree.apply_mutations(nullifier_tree_update).unwrap_or_else(|error| {
216 Self::abort_after_post_commit_failure("nullifier tree", &error)
217 });
218 account_tree.apply_mutations(account_tree_update).unwrap_or_else(|error| {
219 Self::abort_after_post_commit_failure("account tree", &error)
220 });
221 blockchain.push(block_commitment);
222
223 Ok(resolved_note_ids)
224 })?;
225
226 self.block_cache
228 .push(block_num, BlockNotification::new(block_num, cache_bytes))
229 .expect("block cache receives sequential block numbers");
230 let _ = self.committed_tip_tx.send(block_num);
231
232 if let Some(block_lifecycle) = block_lifecycle {
233 block_lifecycle.emit(&resolved_note_ids);
234 }
235 tracing::debug!(target: LOG_TARGET, "Block applied");
236
237 Ok(())
238 }
239
240 fn abort_after_post_commit_failure(component: &str, error: &impl Display) -> ! {
245 tracing::error!(
246 target: LOG_TARGET,
247 component,
248 error = %error,
249 "persistent state update failed after database commit; aborting"
250 );
251
252 #[cfg(test)]
253 panic!("{component} update failed after database commit: {error}");
254
255 #[cfg(not(test))]
256 std::process::abort();
257 }
258
259 pub async fn save_proving_inputs(
261 &self,
262 block_num: BlockNumber,
263 proving_inputs: &BlockProofRequest,
264 ) -> std::io::Result<()> {
265 self.block_store
266 .save_proving_inputs(block_num, &proving_inputs.to_bytes())
267 .await
268 }
269
270 #[miden_instrument(
272 target = COMPONENT,
273 skip_all,
274 err,
275 )]
276 async fn validate_block_header(
277 &self,
278 header: &BlockHeader,
279 body: &BlockBody,
280 ) -> Result<(), ApplyBlockError> {
281 let tx_commitment = body.transactions().commitment();
283 if header.tx_commitment() != tx_commitment {
284 return Err(InvalidBlockError::InvalidBlockTxCommitment {
285 expected: tx_commitment,
286 actual: header.tx_commitment(),
287 }
288 .into());
289 }
290
291 let block_num = header.block_num();
292
293 let prev_block = self
295 .db
296 .select_block_header_by_block_num(None)
297 .await?
298 .ok_or(ApplyBlockError::DbBlockHeaderEmpty)?;
299 let expected_block_num = prev_block.block_num().child();
300 if block_num != expected_block_num {
301 return Err(InvalidBlockError::NewBlockInvalidBlockNum {
302 expected: expected_block_num,
303 submitted: block_num,
304 }
305 .into());
306 }
307 if header.prev_block_commitment() != prev_block.commitment() {
308 return Err(InvalidBlockError::NewBlockInvalidPrevCommitment.into());
309 }
310
311 Ok(())
312 }
313
314 #[miden_instrument(
316 target = COMPONENT,
317 skip_all,
318 err,
319 )]
320 async fn compute_tree_mutations(
321 &self,
322 header: &BlockHeader,
323 body: &BlockBody,
324 ) -> Result<(Word, NullifierMutationSet, Word, AccountMutationSet), ApplyBlockError> {
325 self.with_inner_read_blocking(|inner| {
326 let block_num = header.block_num();
327
328 let duplicate_nullifiers: Vec<_> = body
330 .created_nullifiers()
331 .iter()
332 .filter(|&nullifier| inner.nullifier_tree.get_block_num(nullifier).is_some())
333 .copied()
334 .collect();
335 if !duplicate_nullifiers.is_empty() {
336 return Err(InvalidBlockError::DuplicatedNullifiers(duplicate_nullifiers).into());
337 }
338
339 let peaks = inner.blockchain.peaks();
341 if peaks.hash_peaks() != header.chain_commitment() {
342 return Err(InvalidBlockError::NewBlockInvalidChainCommitment.into());
343 }
344
345 let nullifier_tree_update = inner
347 .nullifier_tree
348 .compute_mutations(
349 body.created_nullifiers().iter().map(|nullifier| (*nullifier, block_num)),
350 )
351 .map_err(InvalidBlockError::NewBlockNullifierAlreadySpent)?;
352
353 if nullifier_tree_update.as_mutation_set().root() != header.nullifier_root() {
354 return Err(InvalidBlockError::NewBlockInvalidNullifierRoot.into());
355 }
356
357 let account_tree_update = inner
359 .account_tree
360 .compute_mutations(
361 body.updated_accounts()
362 .iter()
363 .map(|update| (update.account_id(), update.final_state_commitment())),
364 )
365 .map_err(|e| match e {
366 HistoricalError::AccountTreeError(err) => {
367 InvalidBlockError::NewBlockDuplicateAccountIdPrefix(err)
368 },
369 HistoricalError::MerkleError(_) => {
370 panic!("Unexpected MerkleError during account tree mutation computation")
371 },
372 })?;
373
374 if account_tree_update.as_mutation_set().root() != header.account_root() {
375 return Err(InvalidBlockError::NewBlockInvalidAccountRoot.into());
376 }
377
378 Ok((
379 inner.nullifier_tree.root(),
380 nullifier_tree_update,
381 inner.account_tree.root_latest(),
382 account_tree_update,
383 ))
384 })
385 }
386
387 #[miden_instrument(
389 target = COMPONENT,
390 skip_all,
391 err,
392 )]
393 fn build_note_records(
394 header: &BlockHeader,
395 body: &BlockBody,
396 ) -> Result<Vec<(NoteRecord, Option<Nullifier>)>, ApplyBlockError> {
397 let block_num = header.block_num();
398
399 let note_tree = body.compute_block_note_tree();
400 if note_tree.root() != header.note_root() {
401 return Err(InvalidBlockError::NewBlockInvalidNoteRoot.into());
402 }
403
404 let notes = body
405 .output_notes()
406 .map(|(note_index, note)| {
407 let (details, attachments, nullifier) = match note {
408 OutputNote::Public(public) => (
409 Some(NoteDetails::from(public.as_note())),
410 public.as_note().attachments().clone(),
411 Some(public.as_note().nullifier()),
412 ),
413 OutputNote::Private(private) => (None, private.attachments().clone(), None),
414 };
415
416 let inclusion_path = note_tree.open(note_index);
417
418 let note_record = NoteRecord {
419 block_num,
420 note_index,
421 note_id: note.id().as_word(),
422 metadata: *note.metadata(),
423 details,
424 attachments,
425 inclusion_path,
426 };
427
428 Ok((note_record, nullifier))
429 })
430 .collect::<Result<Vec<_>, InvalidBlockError>>()?;
431
432 Ok(notes)
433 }
434}