node/block_producer/
block_producer_actions.rs

1use std::sync::Arc;
2
3use ledger::scan_state::transaction_logic::valid;
4use mina_p2p_messages::v2::MinaBaseProofStableV2;
5use openmina_core::{block::ArcBlockWithHash, ActionEvent};
6use serde::{Deserialize, Serialize};
7
8use crate::block_producer_effectful::StagedLedgerDiffCreateOutput;
9
10use super::{
11    vrf_evaluator::BlockProducerVrfEvaluatorAction, BlockProducerCurrentState,
12    BlockProducerWonSlot, BlockProducerWonSlotDiscardReason,
13};
14
15pub type BlockProducerActionWithMeta = redux::ActionWithMeta<BlockProducerAction>;
16pub type BlockProducerActionWithMetaRef<'a> = redux::ActionWithMeta<&'a BlockProducerAction>;
17
18#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)]
19#[action_event(level = info)]
20pub enum BlockProducerAction {
21    VrfEvaluator(BlockProducerVrfEvaluatorAction),
22    BestTipUpdate {
23        best_tip: ArcBlockWithHash,
24    },
25    WonSlotSearch,
26    #[action_event(
27        level = info,
28        fields(
29            slot = won_slot.global_slot.slot_number.as_u32(),
30            slot_time = openmina_core::log::to_rfc_3339(won_slot.slot_time)
31                .unwrap_or_else(|_| "<error>".to_owned()),
32            current_time = openmina_core::log::to_rfc_3339(context.timestamp())
33                .unwrap_or_else(|_| "<error>".to_owned()),
34        )
35    )]
36    WonSlot {
37        won_slot: BlockProducerWonSlot,
38    },
39    #[action_event(
40        level = info,
41        fields(
42            reason = format!("{reason:?}"),
43        )
44    )]
45    WonSlotDiscard {
46        reason: BlockProducerWonSlotDiscardReason,
47    },
48    WonSlotWait,
49    WonSlotTransactionsGet,
50    WonSlotTransactionsSuccess {
51        transactions_by_fee: Vec<valid::UserCommand>,
52    },
53    WonSlotProduceInit,
54    StagedLedgerDiffCreateInit,
55    StagedLedgerDiffCreatePending,
56    StagedLedgerDiffCreateSuccess {
57        output: Arc<StagedLedgerDiffCreateOutput>,
58    },
59    BlockUnprovenBuild,
60    BlockProveInit,
61    BlockProvePending,
62    BlockProveSuccess {
63        proof: Arc<MinaBaseProofStableV2>,
64    },
65    BlockProduced,
66    #[action_event(level = trace)]
67    BlockInject,
68    BlockInjected,
69}
70
71impl redux::EnablingCondition<crate::State> for BlockProducerAction {
72    fn is_enabled(&self, state: &crate::State, time: redux::Timestamp) -> bool {
73        match self {
74            BlockProducerAction::VrfEvaluator(a) => a.is_enabled(state, time),
75            BlockProducerAction::BestTipUpdate { .. } => true,
76            BlockProducerAction::WonSlotSearch => state
77                .block_producer
78                .with(None, |this| {
79                    if !this.current.won_slot_should_search() {
80                        return None;
81                    }
82                    if is_syncing_to_produced_block(state) {
83                        return None;
84                    }
85                    let best_tip = state.transition_frontier.best_tip()?;
86                    let cur_global_slot = state.cur_global_slot()?;
87                    let next = this.vrf_evaluator.next_won_slot(cur_global_slot, best_tip);
88                    Some(next.is_some())
89                })
90                .is_some_and(|v| v),
91            BlockProducerAction::WonSlot { won_slot } => state.block_producer.with(false, |this| {
92                let Some(best_tip) = state.transition_frontier.best_tip() else {
93                    return false;
94                };
95                if is_syncing_to_produced_block(state) {
96                    return false;
97                }
98
99                this.current.won_slot_should_search()
100                    && Some(won_slot.global_slot()) >= state.cur_global_slot()
101                    && won_slot > best_tip
102            }),
103            BlockProducerAction::WonSlotWait => state
104                .block_producer
105                .with(false, |this| this.current.won_slot_should_wait(time)),
106            BlockProducerAction::WonSlotProduceInit { .. } => {
107                state.block_producer.with(false, |this| {
108                    let has_genesis_proven_if_needed = || {
109                        state.transition_frontier.best_tip().is_some_and(|tip| {
110                            let proven_block = state.transition_frontier.genesis.proven_block();
111                            !tip.is_genesis()
112                                || proven_block.is_some_and(|b| Arc::ptr_eq(&b.block, &tip.block))
113                        })
114                    };
115                    this.current.won_slot_should_produce(time)
116                        && has_genesis_proven_if_needed()
117                        // don't start block production (particularly staged ledger diff creation),
118                        // if transition frontier sync commit is pending,
119                        // as in case when fork is being committed, there
120                        // is no guarantee that staged ledger for the current
121                        // best tip (chosen as a parent for the new block being produced),
122                        // will be still there, once the staged ledger
123                        // diff creation request reaches the ledger service.
124                        // So we would be trying to build on top of
125                        // non-existent staged ledger causing a failure.
126                        && !state.transition_frontier.sync.is_commit_pending()
127                })
128            }
129            BlockProducerAction::WonSlotTransactionsGet => {
130                state.block_producer.with(false, |this| {
131                    matches!(
132                        this.current,
133                        BlockProducerCurrentState::WonSlotProduceInit { .. }
134                    )
135                })
136            }
137            BlockProducerAction::WonSlotTransactionsSuccess { .. } => {
138                state.block_producer.with(false, |this| {
139                    matches!(
140                        this.current,
141                        BlockProducerCurrentState::WonSlotTransactionsGet { .. }
142                    )
143                })
144            }
145            BlockProducerAction::StagedLedgerDiffCreateInit => {
146                state.block_producer.with(false, |this| {
147                    matches!(
148                        this.current,
149                        BlockProducerCurrentState::WonSlotTransactionsSuccess { .. }
150                    )
151                })
152            }
153            BlockProducerAction::StagedLedgerDiffCreatePending => {
154                state.block_producer.with(false, |this| {
155                    matches!(
156                        this.current,
157                        BlockProducerCurrentState::WonSlotTransactionsSuccess { .. }
158                    )
159                })
160            }
161            BlockProducerAction::StagedLedgerDiffCreateSuccess { .. } => {
162                state.block_producer.with(false, |this| {
163                    matches!(
164                        this.current,
165                        BlockProducerCurrentState::StagedLedgerDiffCreatePending { .. }
166                    )
167                })
168            }
169            BlockProducerAction::BlockUnprovenBuild => state.block_producer.with(false, |this| {
170                matches!(
171                    this.current,
172                    BlockProducerCurrentState::StagedLedgerDiffCreateSuccess { .. }
173                )
174            }),
175            BlockProducerAction::BlockProveInit => state.block_producer.with(false, |this| {
176                matches!(
177                    this.current,
178                    BlockProducerCurrentState::BlockUnprovenBuilt { .. }
179                )
180            }),
181            BlockProducerAction::BlockProvePending => state.block_producer.with(false, |this| {
182                matches!(
183                    this.current,
184                    BlockProducerCurrentState::BlockUnprovenBuilt { .. }
185                )
186            }),
187            BlockProducerAction::BlockProveSuccess { .. } => {
188                state.block_producer.with(false, |this| {
189                    matches!(
190                        this.current,
191                        BlockProducerCurrentState::BlockProvePending { .. }
192                    )
193                })
194            }
195            BlockProducerAction::BlockProduced => state.block_producer.with(false, |this| {
196                matches!(
197                    this.current,
198                    BlockProducerCurrentState::BlockProveSuccess { .. }
199                )
200            }),
201            BlockProducerAction::BlockInject => {
202                state
203                    .block_producer
204                    .with(false, |this| match &this.current {
205                        BlockProducerCurrentState::Produced { block, .. } => {
206                            block
207                                .timestamp()
208                                // broadcast 1s late to account for time drift between nodes
209                                .checked_add(1_000_000_000)
210                                .is_some_and(|block_time| time >= block_time)
211                                && !state.transition_frontier.sync.is_commit_pending()
212                        }
213                        _ => false,
214                    })
215            }
216            BlockProducerAction::BlockInjected => state.block_producer.with(false, |this| {
217                matches!(this.current, BlockProducerCurrentState::Produced { .. })
218            }),
219            BlockProducerAction::WonSlotDiscard { reason } => {
220                let current_reason = state.block_producer.with(None, |bp| {
221                    let best_tip = state.transition_frontier.best_tip()?;
222                    bp.current.won_slot_should_discard(best_tip)
223                });
224                Some(reason) == current_reason.as_ref()
225            }
226        }
227    }
228}
229
230fn is_syncing_to_produced_block(state: &crate::State) -> bool {
231    state
232        .transition_frontier
233        .sync
234        .best_tip()
235        .is_some_and(|tip| state.block_producer.is_me(tip.producer()))
236}