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 && !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 .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}