node/block_producer/
block_producer_reducer.rs

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    /// Substate is accesses from global state, because applied blocks from transition frontier are required
37    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                // set the genesis timestamp on the first best tip update
59                // TODO: move/remove once we can generate the genesis block
60                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                            // We are producing block which replaces current best tip
113                            // instead of extending it.
114                            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                // Store the produced block in stats, used by heartbeats
311                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                        // comment from Mina repo: (* TODO: We need to make sure issue #2328 is properly addressed. *)
471                        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            // TODO(binier): when should this be false?
516            // <https://github.com/MinaProtocol/mina/blob/4aac38814556b9641ffbdfaef19b38ab7980011b/src/lib/consensus/proof_of_stake.ml#L2864>
517            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                // TODO(binier): test
650                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()) // TODO: Handle error ?
659                        .map(v2::StateBodyHash::from)
660                        .collect();
661                    (first_hash, body_hashes)
662                } else {
663                    // TODO: test this as well
664                    // If the chain is empty, return the same as when chain_proof_len is 0
665                    (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        // None if the evaluator is not evaluating
724        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 we receive a block with higher epoch than the current one, interrupt the evaluation
731            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        // Should be impossible, we call this immediately after having injected the block.
778        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}