1use ledger::scan_state::currency::{Amount, Signed};
2use mina_p2p_messages::{list::List, v2};
3use openmina_core::{
4 block::ArcBlockWithHash,
5 bug_condition,
6 consensus::{
7 global_sub_window, in_same_checkpoint_window, in_seed_update_range, relative_sub_window,
8 ConsensusConstants,
9 },
10 constants::constraint_constants,
11};
12use p2p::P2pNetworkPubsubAction;
13use redux::{callback, Dispatcher, Timestamp};
14
15use crate::{
16 transition_frontier::sync::TransitionFrontierSyncAction, Action, BlockProducerEffectfulAction,
17 State, Substate, TransactionPoolAction,
18};
19
20use super::{
21 calc_epoch_seed, next_epoch_first_slot, to_epoch_and_slot,
22 vrf_evaluator::{
23 BlockProducerVrfEvaluatorAction, BlockProducerVrfEvaluatorState, InterruptReason,
24 },
25 BlockProducerAction, BlockProducerActionWithMetaRef, BlockProducerCurrentState,
26 BlockProducerEnabled, BlockProducerState, BlockWithoutProof,
27};
28
29impl BlockProducerState {
30 pub fn reducer(state_context: Substate<State>, action: BlockProducerActionWithMetaRef<'_>) {
31 BlockProducerEnabled::reducer(state_context, action);
32 }
33}
34
35impl BlockProducerEnabled {
36 pub fn reducer(mut state_context: Substate<State>, action: BlockProducerActionWithMetaRef<'_>) {
38 let (action, meta) = action.split();
39 let Ok(global_state) = state_context.get_substate_mut() else {
40 return;
41 };
42 let consensus_constants = &global_state.config.consensus_constants;
43
44 let best_chain = &global_state.transition_frontier.best_chain;
45 let Some(state) = global_state.block_producer.as_mut() else {
46 return;
47 };
48
49 match action {
50 BlockProducerAction::VrfEvaluator(action) => {
51 BlockProducerVrfEvaluatorState::reducer(
52 Substate::from_compatible_substate(state_context),
53 meta.with_action(action),
54 );
55 }
56 BlockProducerAction::BestTipUpdate { best_tip } => {
57 state.injected_blocks.remove(best_tip.hash());
58 if state.vrf_evaluator.genesis_timestamp == redux::Timestamp::ZERO {
61 state.vrf_evaluator.genesis_timestamp = best_tip.genesis_timestamp();
62 }
63
64 let (dispatcher, state) = state_context.into_dispatcher_and_state();
65 Self::dispatch_best_tip_update(dispatcher, state, best_tip);
66 }
67 BlockProducerAction::WonSlotSearch => {
68 let (dispatcher, state) = state_context.into_dispatcher_and_state();
69 if let Some(won_slot) = state.block_producer.with(None, |bp| {
70 let best_tip = state.transition_frontier.best_tip()?;
71 let cur_global_slot = state.cur_global_slot()?;
72 bp.vrf_evaluator.next_won_slot(cur_global_slot, best_tip)
73 }) {
74 dispatcher.push(BlockProducerAction::WonSlot { won_slot });
75 }
76 }
77 BlockProducerAction::WonSlot { won_slot } => {
78 state.current = BlockProducerCurrentState::WonSlot {
79 time: meta.time(),
80 won_slot: won_slot.clone(),
81 };
82
83 let dispatcher = state_context.into_dispatcher();
84 dispatcher.push(BlockProducerEffectfulAction::WonSlot {
85 won_slot: won_slot.clone(),
86 });
87 }
88 BlockProducerAction::WonSlotDiscard { reason } => {
89 if let Some(won_slot) = state.current.won_slot() {
90 state.current = BlockProducerCurrentState::WonSlotDiscarded {
91 time: meta.time(),
92 won_slot: won_slot.clone(),
93 reason: *reason,
94 };
95 }
96
97 let dispatcher = state_context.into_dispatcher();
98 dispatcher.push(BlockProducerEffectfulAction::WonSlotDiscard { reason: *reason });
99 }
100 BlockProducerAction::WonSlotWait => {
101 if let Some(won_slot) = state.current.won_slot() {
102 state.current = BlockProducerCurrentState::WonSlotWait {
103 time: meta.time(),
104 won_slot: won_slot.clone(),
105 };
106 }
107 }
108 BlockProducerAction::WonSlotProduceInit => {
109 if let Some(won_slot) = state.current.won_slot() {
110 if let Some(chain) = best_chain.last().map(|best_tip| {
111 if best_tip.global_slot() == won_slot.global_slot() {
112 best_chain
115 .get(..best_chain.len().saturating_sub(1))
116 .unwrap_or(&[])
117 .to_vec()
118 } else {
119 best_chain.to_vec()
120 }
121 }) {
122 state.current = BlockProducerCurrentState::WonSlotProduceInit {
123 time: meta.time(),
124 won_slot: won_slot.clone(),
125 chain,
126 };
127 };
128 }
129
130 let dispatcher = state_context.into_dispatcher();
131 dispatcher.push(BlockProducerAction::WonSlotTransactionsGet);
132 }
133 BlockProducerAction::WonSlotTransactionsGet => {
134 let BlockProducerCurrentState::WonSlotProduceInit {
135 won_slot, chain, ..
136 } = &mut state.current
137 else {
138 bug_condition!("Invalid state for `BlockProducerAction::WonSlotTransactionsGet` expected: `BlockProducerCurrentState::WonSlotProduceInit`, found: {:?}", state.current);
139 return;
140 };
141
142 state.current = BlockProducerCurrentState::WonSlotTransactionsGet {
143 time: meta.time(),
144 won_slot: won_slot.clone(),
145 chain: chain.clone(),
146 };
147
148 let dispatcher = state_context.into_dispatcher();
149 dispatcher.push(TransactionPoolAction::CollectTransactionsByFee);
150 }
151 BlockProducerAction::WonSlotTransactionsSuccess {
152 transactions_by_fee,
153 } => {
154 let BlockProducerCurrentState::WonSlotTransactionsGet {
155 won_slot, chain, ..
156 } = &mut state.current
157 else {
158 bug_condition!("Invalid state for `BlockProducerAction::WonSlotTransactionsSuccess` expected: `BlockProducerCurrentState::WonSlotTransactionsGet`, found: {:?}", state.current);
159 return;
160 };
161
162 state.current = BlockProducerCurrentState::WonSlotTransactionsSuccess {
163 time: meta.time(),
164 won_slot: won_slot.clone(),
165 chain: chain.clone(),
166 transactions_by_fee: transactions_by_fee.clone(),
167 };
168
169 let dispatcher = state_context.into_dispatcher();
170 dispatcher.push(BlockProducerAction::StagedLedgerDiffCreateInit);
171 }
172 BlockProducerAction::StagedLedgerDiffCreateInit => {
173 let dispatcher = state_context.into_dispatcher();
174 dispatcher.push(BlockProducerEffectfulAction::StagedLedgerDiffCreateInit);
175 }
176 BlockProducerAction::StagedLedgerDiffCreatePending => {
177 let BlockProducerCurrentState::WonSlotTransactionsSuccess {
178 won_slot,
179 chain,
180 transactions_by_fee,
181 ..
182 } = &mut state.current
183 else {
184 bug_condition!("Invalid state for `BlockProducerAction::StagedLedgerDiffCreatePending` expected: `BlockProducerCurrentState::WonSlotTransactionsSuccess`, found: {:?}", state.current);
185 return;
186 };
187 state.current = BlockProducerCurrentState::StagedLedgerDiffCreatePending {
188 time: meta.time(),
189 won_slot: won_slot.clone(),
190 chain: std::mem::take(chain),
191 transactions_by_fee: transactions_by_fee.to_vec(),
192 };
193 }
194 BlockProducerAction::StagedLedgerDiffCreateSuccess { output } => {
195 let BlockProducerCurrentState::StagedLedgerDiffCreatePending {
196 won_slot,
197 chain,
198 ..
199 } = &mut state.current
200 else {
201 bug_condition!("Invalid state for `BlockProducerAction::StagedLedgerDiffCreateSuccess` expected: `BlockProducerCurrentState::StagedLedgerDiffCreatePending`, found: {:?}", state.current);
202 return;
203 };
204 state.current = BlockProducerCurrentState::StagedLedgerDiffCreateSuccess {
205 time: meta.time(),
206 won_slot: won_slot.clone(),
207 chain: std::mem::take(chain),
208 diff: output.diff.clone(),
209 diff_hash: output.diff_hash.clone(),
210 staged_ledger_hash: output.staged_ledger_hash.clone(),
211 emitted_ledger_proof: output.emitted_ledger_proof.clone(),
212 pending_coinbase_update: output.pending_coinbase_update.clone(),
213 pending_coinbase_witness: output.pending_coinbase_witness.clone(),
214 stake_proof_sparse_ledger: output.stake_proof_sparse_ledger.clone(),
215 };
216
217 let dispatcher = state_context.into_dispatcher();
218 dispatcher.push(BlockProducerEffectfulAction::StagedLedgerDiffCreateSuccess);
219 }
220 BlockProducerAction::BlockUnprovenBuild => {
221 state.reduce_block_unproved_build(consensus_constants, meta.time());
222
223 let dispatcher = state_context.into_dispatcher();
224 dispatcher.push(BlockProducerEffectfulAction::BlockUnprovenBuild);
225 }
226 BlockProducerAction::BlockProveInit => {
227 let dispatcher = state_context.into_dispatcher();
228 dispatcher.push(BlockProducerEffectfulAction::BlockProveInit);
229 }
230 BlockProducerAction::BlockProvePending => {
231 let current_state = std::mem::take(&mut state.current);
232
233 if let BlockProducerCurrentState::BlockUnprovenBuilt {
234 won_slot,
235 chain,
236 emitted_ledger_proof,
237 pending_coinbase_update,
238 pending_coinbase_witness,
239 stake_proof_sparse_ledger,
240 block,
241 block_hash,
242 ..
243 } = current_state
244 {
245 state.current = BlockProducerCurrentState::BlockProvePending {
246 time: meta.time(),
247 won_slot,
248 chain,
249 emitted_ledger_proof,
250 pending_coinbase_update,
251 pending_coinbase_witness,
252 stake_proof_sparse_ledger,
253 block,
254 block_hash,
255 };
256 } else {
257 bug_condition!("Invalid state for `BlockProducerAction::BlockProvePending` expected: `BlockProducerCurrentState::BlockUnprovenBuilt`, found: {:?}", current_state);
258 }
259 }
260 BlockProducerAction::BlockProveSuccess { proof } => {
261 let current_state = std::mem::take(&mut state.current);
262
263 if let BlockProducerCurrentState::BlockProvePending {
264 won_slot,
265 chain,
266 block,
267 block_hash,
268 ..
269 } = current_state
270 {
271 state.current = BlockProducerCurrentState::BlockProveSuccess {
272 time: meta.time(),
273 won_slot,
274 chain,
275 block,
276 block_hash,
277 proof: proof.clone(),
278 };
279 } else {
280 bug_condition!("Invalid state for `BlockProducerAction::BlockProveSuccess` expected: `BlockProducerCurrentState::BlockProvePending`, found: {:?}", current_state);
281 }
282
283 let dispatcher = state_context.into_dispatcher();
284 dispatcher.push(BlockProducerEffectfulAction::BlockProveSuccess);
285 }
286 BlockProducerAction::BlockProduced => {
287 let current_state = std::mem::take(&mut state.current);
288
289 if let BlockProducerCurrentState::BlockProveSuccess {
290 won_slot,
291 chain,
292 block,
293 block_hash,
294 proof,
295 ..
296 } = current_state
297 {
298 state.current = BlockProducerCurrentState::Produced {
299 time: meta.time(),
300 won_slot,
301 chain,
302 block: block.with_hash_and_proof(block_hash, proof),
303 };
304 } else {
305 bug_condition!("Invalid state for `BlockProducerAction::BlockProduced` expected: `BlockProducerCurrentState::BlockProveSuccess`, found: {:?}", current_state);
306 }
307
308 let (dispatcher, global_state) = state_context.into_dispatcher_and_state();
309
310 let block = global_state
312 .block_producer
313 .as_ref()
314 .and_then(|bp| bp.current.produced_block())
315 .cloned();
316 if let Some(block) = block {
317 dispatcher.push(BlockProducerEffectfulAction::BlockProduced { block });
318 }
319
320 dispatcher.push(BlockProducerAction::BlockInject);
321 }
322 BlockProducerAction::BlockInject => {
323 let (dispatcher, state) = state_context.into_dispatcher_and_state();
324
325 let Some((best_tip, root_block, blocks_inbetween)) = None.or_else(|| {
326 let (best_tip, chain) = state.block_producer.produced_block_with_chain()?;
327 let mut iter = chain.iter();
328 let root_block = iter.next()?.block_with_hash();
329 let blocks_inbetween = iter.map(|b| b.hash().clone()).collect();
330 Some((best_tip.clone(), root_block.clone(), blocks_inbetween))
331 }) else {
332 bug_condition!("Invalid state for `BlockProducerAction::BlockInject`: did not find best_tip/root_block in block producer");
333 return;
334 };
335
336 let previous_root_snarked_ledger_hash = state
337 .transition_frontier
338 .root()
339 .map(|b| b.snarked_ledger_hash().clone());
340
341 dispatcher.push(TransitionFrontierSyncAction::BestTipUpdate {
342 previous_root_snarked_ledger_hash,
343 best_tip: best_tip.clone(),
344 root_block,
345 blocks_inbetween,
346 on_success: Some(callback!(
347 on_transition_frontier_sync_best_tip_update(_p: ()) -> crate::Action{
348 BlockProducerAction::BlockInjected
349 }
350 )),
351 });
352 }
353 BlockProducerAction::BlockInjected => {
354 if let BlockProducerCurrentState::Produced {
355 won_slot,
356 chain,
357 block,
358 ..
359 } = &mut state.current
360 {
361 state.injected_blocks.insert(block.hash().clone());
362 state.current = BlockProducerCurrentState::Injected {
363 time: meta.time(),
364 won_slot: won_slot.clone(),
365 chain: std::mem::take(chain),
366 block: block.clone(),
367 };
368 } else {
369 bug_condition!("Invalid state for `BlockProducerAction::BlockInjected` expected: `BlockProducerCurrentState::Produced`, found: {:?}", state.current);
370 }
371
372 let (dispatcher, global_state) = state_context.into_dispatcher_and_state();
373
374 #[cfg(feature = "p2p-libp2p")]
375 broadcast_injected_block(global_state, dispatcher);
376
377 dispatcher.push(BlockProducerAction::WonSlotSearch);
378 }
379 }
380 }
381
382 fn reduce_block_unproved_build(
383 &mut self,
384 consensus_constants: &ConsensusConstants,
385 time: Timestamp,
386 ) {
387 let current_state = std::mem::take(&mut self.current);
388
389 let BlockProducerCurrentState::StagedLedgerDiffCreateSuccess {
390 won_slot,
391 chain,
392 diff,
393 diff_hash,
394 staged_ledger_hash,
395 emitted_ledger_proof,
396 pending_coinbase_update,
397 pending_coinbase_witness,
398 stake_proof_sparse_ledger,
399 ..
400 } = current_state
401 else {
402 bug_condition!("Invalid state for `BlockProducerAction::BlockUnprovenBuild` expected: `BlockProducerCurrentState::StagedLedgerDiffCreateSuccess`, found: {:?}", current_state);
403 return;
404 };
405 let Some(pred_block) = chain.last() else {
406 bug_condition!("Invalid state for `BlockProducerAction::BlockUnprovenBuild`: did not find predecessor block");
407 return;
408 };
409
410 let pred_consensus_state = &pred_block.header().protocol_state.body.consensus_state;
411 let pred_blockchain_state = &pred_block.header().protocol_state.body.blockchain_state;
412
413 let genesis_ledger_hash = &pred_blockchain_state.genesis_ledger_hash;
414
415 let block_timestamp = won_slot.timestamp();
416 let pred_global_slot = pred_consensus_state
417 .curr_global_slot_since_hard_fork
418 .clone();
419 let curr_global_slot_since_hard_fork = won_slot.global_slot.clone();
420 let global_slot_since_genesis =
421 won_slot.global_slot_since_genesis(pred_block.global_slot_diff());
422 let (pred_epoch, _) = to_epoch_and_slot(&pred_global_slot);
423 let (next_epoch, next_slot) = to_epoch_and_slot(&curr_global_slot_since_hard_fork);
424 let has_ancestor_in_same_checkpoint_window =
425 in_same_checkpoint_window(&pred_global_slot, &curr_global_slot_since_hard_fork);
426
427 let block_stake_winner = won_slot.delegator.0.clone();
428 let vrf_truncated_output: v2::ConsensusVrfOutputTruncatedStableV1 =
429 (*won_slot.vrf_output).clone().into();
430 let vrf_hash = won_slot.vrf_output.hash();
431 let block_creator = self.config.pub_key.clone();
432 let coinbase_receiver = self.config.coinbase_receiver().clone();
433 let proposed_protocol_version_opt = self.config.proposed_protocol_version.clone();
434
435 let ledger_proof_statement = ledger_proof_statement_from_emitted_proof(
436 emitted_ledger_proof.as_deref(),
437 &pred_blockchain_state.ledger_proof_statement,
438 );
439
440 let supply_increase = emitted_ledger_proof.as_ref().map_or(Signed::zero(), |v| {
441 Signed::from(&v.statement.supply_increase)
442 });
443
444 let total_currency = {
445 let (amount, overflowed) = Amount::from(pred_consensus_state.total_currency.clone())
446 .add_signed_flagged(supply_increase);
447 if overflowed {
448 todo!("total_currency overflowed");
449 }
450 amount
451 };
452
453 let (staking_epoch_data, next_epoch_data, epoch_count) = {
454 let next_staking_ledger = if pred_block.snarked_ledger_hash() == genesis_ledger_hash {
455 pred_consensus_state.next_epoch_data.ledger.clone()
456 } else {
457 v2::MinaBaseEpochLedgerValueStableV1 {
458 hash: pred_block.snarked_ledger_hash().clone(),
459 total_currency: (&total_currency).into(),
460 }
461 };
462 let (staking_data, next_data, epoch_count) = if next_epoch > pred_epoch {
463 let staking_data =
464 next_to_staking_epoch_data(&pred_consensus_state.next_epoch_data);
465 let next_data =
466 v2::ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 {
467 seed: pred_consensus_state.next_epoch_data.seed.clone(),
468 ledger: next_staking_ledger,
469 start_checkpoint: pred_block.hash().clone(),
470 lock_checkpoint: v2::StateHash::zero(),
472 epoch_length: v2::UnsignedExtendedUInt32StableV1(1.into()),
473 };
474 let epoch_count = v2::UnsignedExtendedUInt32StableV1(
475 (pred_consensus_state
476 .epoch_count
477 .as_u32()
478 .checked_add(1)
479 .expect("overflow"))
480 .into(),
481 );
482 (staking_data, next_data, epoch_count)
483 } else {
484 assert_eq!(pred_epoch, next_epoch);
485 let mut next_data = pred_consensus_state.next_epoch_data.clone();
486 next_data.epoch_length = v2::UnsignedExtendedUInt32StableV1(
487 (next_data
488 .epoch_length
489 .as_u32()
490 .checked_add(1)
491 .expect("overflow"))
492 .into(),
493 );
494 (
495 pred_consensus_state.staking_epoch_data.clone(),
496 next_data,
497 pred_consensus_state.epoch_count,
498 )
499 };
500
501 let next_data = if in_seed_update_range(next_slot, pred_block.constants()) {
502 v2::ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 {
503 seed: calc_epoch_seed(&next_data.seed, vrf_hash),
504 lock_checkpoint: pred_block.hash().clone(),
505 ..next_data
506 }
507 } else {
508 next_data
509 };
510
511 (staking_data, next_data, epoch_count)
512 };
513
514 let (min_window_density, sub_window_densities) = {
515 let incr_window = true;
518 let pred_sub_window_densities = &pred_consensus_state.sub_window_densities;
519
520 let pred_global_sub_window =
521 global_sub_window(&pred_global_slot, pred_block.constants());
522 let next_global_sub_window =
523 global_sub_window(&curr_global_slot_since_hard_fork, pred_block.constants());
524
525 let pred_relative_sub_window = relative_sub_window(pred_global_sub_window);
526 let next_relative_sub_window = relative_sub_window(next_global_sub_window);
527
528 let is_same_global_sub_window = pred_global_sub_window == next_global_sub_window;
529 let are_windows_overlapping = pred_global_sub_window
530 .checked_add(constraint_constants().sub_windows_per_window as u32)
531 .expect("overflow")
532 >= next_global_sub_window;
533
534 let current_sub_window_densities = pred_sub_window_densities
535 .iter()
536 .enumerate()
537 .map(|(i, density)| (i as u32, density.as_u32()))
538 .map(|(i, density)| {
539 let gt_pred_sub_window = i > pred_relative_sub_window;
540 let lt_next_sub_window = i < next_relative_sub_window;
541 let within_range = if pred_relative_sub_window < next_relative_sub_window {
542 gt_pred_sub_window && lt_next_sub_window
543 } else {
544 gt_pred_sub_window || lt_next_sub_window
545 };
546 if is_same_global_sub_window || (are_windows_overlapping && !within_range) {
547 density
548 } else {
549 0
550 }
551 })
552 .collect::<Vec<_>>();
553
554 let grace_period_end = consensus_constants.grace_period_end;
555 let min_window_density = if is_same_global_sub_window
556 || curr_global_slot_since_hard_fork.slot_number.as_u32() < grace_period_end
557 {
558 pred_consensus_state.min_window_density
559 } else {
560 let cur_density = current_sub_window_densities.iter().sum();
561 let min_density = pred_consensus_state
562 .min_window_density
563 .as_u32()
564 .min(cur_density);
565 v2::UnsignedExtendedUInt32StableV1(min_density.into())
566 };
567
568 let next_sub_window_densities = current_sub_window_densities
569 .into_iter()
570 .enumerate()
571 .map(|(i, density)| (i as u32, density))
572 .map(|(i, density)| {
573 let is_next_sub_window = i == next_relative_sub_window;
574 if is_next_sub_window {
575 let density = if is_same_global_sub_window {
576 density
577 } else {
578 0
579 };
580 if incr_window {
581 density.saturating_add(1)
582 } else {
583 density
584 }
585 } else {
586 density
587 }
588 })
589 .map(|v| v2::UnsignedExtendedUInt32StableV1(v.into()))
590 .collect();
591
592 (min_window_density, next_sub_window_densities)
593 };
594
595 let supercharge_coinbase = can_apply_supercharged_coinbase(
596 &block_stake_winner,
597 &stake_proof_sparse_ledger,
598 &global_slot_since_genesis,
599 );
600 let consensus_state = v2::ConsensusProofOfStakeDataConsensusStateValueStableV2 {
601 blockchain_length: v2::UnsignedExtendedUInt32StableV1(
602 (pred_block.height().checked_add(1).expect("overflow")).into(),
603 ),
604 epoch_count,
605 min_window_density,
606 sub_window_densities,
607 last_vrf_output: vrf_truncated_output,
608 total_currency: (&total_currency).into(),
609 curr_global_slot_since_hard_fork,
610 global_slot_since_genesis,
611 staking_epoch_data,
612 next_epoch_data,
613 has_ancestor_in_same_checkpoint_window,
614 block_stake_winner,
615 block_creator,
616 coinbase_receiver,
617 supercharge_coinbase,
618 };
619
620 let protocol_state = v2::MinaStateProtocolStateValueStableV2 {
621 previous_state_hash: pred_block.hash().clone(),
622 body: v2::MinaStateProtocolStateBodyValueStableV2 {
623 genesis_state_hash: if pred_block.is_genesis() {
624 pred_block.hash().clone()
625 } else {
626 pred_block
627 .header()
628 .protocol_state
629 .body
630 .genesis_state_hash
631 .clone()
632 },
633 constants: pred_block.header().protocol_state.body.constants.clone(),
634 blockchain_state: v2::MinaStateBlockchainStateValueStableV2 {
635 staged_ledger_hash: staged_ledger_hash.clone(),
636 genesis_ledger_hash: genesis_ledger_hash.clone(),
637 ledger_proof_statement,
638 timestamp: block_timestamp,
639 body_reference: diff_hash.clone(),
640 },
641 consensus_state,
642 },
643 };
644
645 let chain_proof_len = pred_block.constants().delta.as_u32() as usize;
646 let delta_block_chain_proof = match chain_proof_len {
647 0 => (pred_block.hash().clone(), List::new()),
648 chain_proof_len => {
649 let mut iter = chain
651 .iter()
652 .rev()
653 .take(chain_proof_len.saturating_add(1))
654 .rev();
655 if let Some(first_block) = iter.next() {
656 let first_hash = first_block.hash().clone();
657 let body_hashes = iter
658 .filter_map(|b| b.header().protocol_state.body.try_hash().ok()) .map(v2::StateBodyHash::from)
660 .collect();
661 (first_hash, body_hashes)
662 } else {
663 (pred_block.hash().clone(), List::new())
666 }
667 }
668 };
669
670 let block = BlockWithoutProof {
671 protocol_state,
672 delta_block_chain_proof,
673 current_protocol_version: pred_block.header().current_protocol_version.clone(),
674 proposed_protocol_version_opt,
675 body: v2::StagedLedgerDiffBodyStableV1 {
676 staged_ledger_diff: diff.clone(),
677 },
678 };
679 let Ok(block_hash) = block.protocol_state.try_hash() else {
680 openmina_core::log::inner::error!("Invalid protocol state");
681 return;
682 };
683
684 self.current = BlockProducerCurrentState::BlockUnprovenBuilt {
685 time,
686 won_slot,
687 chain,
688 emitted_ledger_proof,
689 pending_coinbase_update,
690 pending_coinbase_witness,
691 stake_proof_sparse_ledger,
692 block,
693 block_hash,
694 };
695 }
696
697 fn dispatch_best_tip_update(
698 dispatcher: &mut Dispatcher<Action, State>,
699 state: &State,
700 best_tip: &ArcBlockWithHash,
701 ) {
702 let global_slot = best_tip
703 .consensus_state()
704 .curr_global_slot_since_hard_fork
705 .clone();
706
707 let (best_tip_epoch, best_tip_slot) = to_epoch_and_slot(&global_slot);
708 let root_block_epoch = if let Some(root_block) = state.transition_frontier.root() {
709 let root_block_global_slot = root_block.curr_global_slot_since_hard_fork();
710 to_epoch_and_slot(root_block_global_slot).0
711 } else {
712 bug_condition!("Expected to find a block at the root of the transition frontier but there was none");
713 best_tip_epoch.saturating_sub(1)
714 };
715 let next_epoch_first_slot = next_epoch_first_slot(&global_slot);
716 let current_epoch = state.current_epoch();
717 let current_slot = state.current_slot();
718
719 dispatcher.push(BlockProducerVrfEvaluatorAction::InitializeEvaluator {
720 best_tip: best_tip.clone(),
721 });
722
723 let currenty_evaluated_epoch = state
725 .block_producer
726 .vrf_evaluator()
727 .and_then(|vrf_evaluator| vrf_evaluator.currently_evaluated_epoch());
728
729 if let Some(currently_evaluated_epoch) = currenty_evaluated_epoch {
730 if currently_evaluated_epoch < best_tip_epoch {
732 dispatcher.push(BlockProducerVrfEvaluatorAction::InterruptEpochEvaluation {
733 reason: InterruptReason::BestTipWithHigherEpoch,
734 });
735 }
736 }
737
738 let is_next_epoch_seed_finalized = if let Some(current_slot) = current_slot {
739 !in_seed_update_range(current_slot, best_tip.constants())
740 } else {
741 false
742 };
743
744 dispatcher.push(BlockProducerVrfEvaluatorAction::CheckEpochEvaluability {
745 current_epoch,
746 is_next_epoch_seed_finalized,
747 root_block_epoch,
748 best_tip_epoch,
749 best_tip_slot,
750 best_tip_global_slot: best_tip.global_slot(),
751 next_epoch_first_slot,
752 staking_epoch_data: Box::new(best_tip.consensus_state().staking_epoch_data.clone()),
753 next_epoch_data: Box::new(best_tip.consensus_state().next_epoch_data.clone()),
754 });
755
756 if let Some(reason) = state
757 .block_producer
758 .with(None, |bp| bp.current.won_slot_should_discard(best_tip))
759 {
760 dispatcher.push(BlockProducerAction::WonSlotDiscard { reason });
761 } else {
762 dispatcher.push(BlockProducerAction::WonSlotSearch);
763 }
764 }
765}
766
767#[cfg(feature = "p2p-libp2p")]
768fn broadcast_injected_block(global_state: &State, dispatcher: &mut Dispatcher<Action, State>) {
769 use mina_p2p_messages::gossip::GossipNetMessageV2;
770
771 let Some(block) = global_state
772 .block_producer
773 .as_ref()
774 .and_then(|bp| bp.current.injected_block())
775 .map(|pb| pb.block.clone())
776 else {
777 return;
779 };
780
781 let message = GossipNetMessageV2::NewState(block);
782 dispatcher.push(P2pNetworkPubsubAction::Broadcast { message });
783}
784
785fn can_apply_supercharged_coinbase(
786 block_stake_winner: &v2::NonZeroCurvePoint,
787 stake_proof_sparse_ledger: &v2::MinaBaseSparseLedgerBaseStableV2,
788 global_slot_since_genesis: &v2::MinaNumbersGlobalSlotSinceGenesisMStableV1,
789) -> bool {
790 use ledger::staged_ledger::staged_ledger::StagedLedger;
791
792 let winner = (block_stake_winner)
793 .try_into()
794 .expect("Public key being used cannot be invalid here");
795 let epoch_ledger = (stake_proof_sparse_ledger)
796 .try_into()
797 .expect("Sparse ledger being used cannot be invalid here");
798 let global_slot = (global_slot_since_genesis).into();
799
800 StagedLedger::can_apply_supercharged_coinbase_exn(winner, &epoch_ledger, global_slot)
801}
802
803fn next_to_staking_epoch_data(
804 data: &v2::ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1,
805) -> v2::ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 {
806 v2::ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 {
807 seed: data.seed.clone(),
808 ledger: data.ledger.clone(),
809 start_checkpoint: data.start_checkpoint.clone(),
810 lock_checkpoint: data.lock_checkpoint.clone(),
811 epoch_length: data.epoch_length,
812 }
813}
814
815fn ledger_proof_statement_from_emitted_proof(
816 emitted_ledger_proof: Option<&v2::LedgerProofProdStableV2>,
817 pred_proof_statement: &v2::MinaStateBlockchainStateValueStableV2LedgerProofStatement,
818) -> v2::MinaStateBlockchainStateValueStableV2LedgerProofStatement {
819 match emitted_ledger_proof.map(|proof| &proof.statement) {
820 None => pred_proof_statement.clone(),
821 Some(stmt) => v2::MinaStateBlockchainStateValueStableV2LedgerProofStatement {
822 source: stmt.source.clone(),
823 target: stmt.target.clone(),
824 connecting_ledger_left: stmt.connecting_ledger_left.clone(),
825 connecting_ledger_right: stmt.connecting_ledger_right.clone(),
826 supply_increase: stmt.supply_increase.clone(),
827 fee_excess: stmt.fee_excess.clone(),
828 sok_digest: (),
829 },
830 }
831}