1use std::{collections::BTreeSet, sync::Arc, time::Duration};
2
3use ledger::scan_state::transaction_logic::valid;
4use mina_p2p_messages::v2;
5use openmina_core::{
6 block::{AppliedBlock, ArcBlockWithHash},
7 consensus::consensus_take,
8};
9use serde::{Deserialize, Serialize};
10
11use crate::account::AccountPublicKey;
12
13use super::{
14 vrf_evaluator::BlockProducerVrfEvaluatorState, BlockProducerConfig, BlockProducerWonSlot,
15 BlockWithoutProof,
16};
17
18#[derive(Serialize, Deserialize, Debug, Clone)]
19pub struct BlockProducerState(Option<BlockProducerEnabled>);
20
21#[derive(Serialize, Deserialize, Debug, Clone)]
22pub struct BlockProducerEnabled {
23 pub config: BlockProducerConfig,
24 pub vrf_evaluator: BlockProducerVrfEvaluatorState,
25 pub current: BlockProducerCurrentState,
26 pub injected_blocks: BTreeSet<v2::StateHash>,
29}
30
31#[derive(Serialize, Deserialize, Debug, Clone)]
32pub enum BlockProducerCurrentState {
33 Idle {
34 time: redux::Timestamp,
35 },
36 WonSlotDiscarded {
37 time: redux::Timestamp,
38 won_slot: BlockProducerWonSlot,
39 reason: BlockProducerWonSlotDiscardReason,
40 },
41 WonSlot {
42 time: redux::Timestamp,
43 won_slot: BlockProducerWonSlot,
44 },
45 WonSlotWait {
46 time: redux::Timestamp,
47 won_slot: BlockProducerWonSlot,
48 },
49 WonSlotProduceInit {
50 time: redux::Timestamp,
51 won_slot: BlockProducerWonSlot,
52 chain: Vec<AppliedBlock>,
54 },
55 WonSlotTransactionsGet {
56 time: redux::Timestamp,
57 won_slot: BlockProducerWonSlot,
58 chain: Vec<AppliedBlock>,
60 },
61 WonSlotTransactionsSuccess {
62 time: redux::Timestamp,
63 won_slot: BlockProducerWonSlot,
64 chain: Vec<AppliedBlock>,
66 transactions_by_fee: Vec<valid::UserCommand>,
67 },
68 StagedLedgerDiffCreatePending {
69 time: redux::Timestamp,
70 won_slot: BlockProducerWonSlot,
71 chain: Vec<AppliedBlock>,
73 transactions_by_fee: Vec<valid::UserCommand>,
74 },
75 StagedLedgerDiffCreateSuccess {
76 time: redux::Timestamp,
77 won_slot: BlockProducerWonSlot,
78 chain: Vec<AppliedBlock>,
80 diff: v2::StagedLedgerDiffDiffStableV2,
81 diff_hash: v2::ConsensusBodyReferenceStableV1,
83 staged_ledger_hash: v2::MinaBaseStagedLedgerHashStableV1,
84 emitted_ledger_proof: Option<Arc<v2::LedgerProofProdStableV2>>,
85 pending_coinbase_update: v2::MinaBasePendingCoinbaseUpdateStableV1,
86 pending_coinbase_witness: v2::MinaBasePendingCoinbaseWitnessStableV2,
87 stake_proof_sparse_ledger: v2::MinaBaseSparseLedgerBaseStableV2,
88 },
89 BlockUnprovenBuilt {
90 time: redux::Timestamp,
91 won_slot: BlockProducerWonSlot,
92 chain: Vec<AppliedBlock>,
94 emitted_ledger_proof: Option<Arc<v2::LedgerProofProdStableV2>>,
95 pending_coinbase_update: v2::MinaBasePendingCoinbaseUpdateStableV1,
96 pending_coinbase_witness: v2::MinaBasePendingCoinbaseWitnessStableV2,
97 stake_proof_sparse_ledger: v2::MinaBaseSparseLedgerBaseStableV2,
98 block: BlockWithoutProof,
99 block_hash: v2::StateHash,
100 },
101 BlockProvePending {
102 time: redux::Timestamp,
103 won_slot: BlockProducerWonSlot,
104 chain: Vec<AppliedBlock>,
106 emitted_ledger_proof: Option<Arc<v2::LedgerProofProdStableV2>>,
107 pending_coinbase_update: v2::MinaBasePendingCoinbaseUpdateStableV1,
108 pending_coinbase_witness: v2::MinaBasePendingCoinbaseWitnessStableV2,
109 stake_proof_sparse_ledger: v2::MinaBaseSparseLedgerBaseStableV2,
110 block: BlockWithoutProof,
111 block_hash: v2::StateHash,
112 },
113 BlockProveSuccess {
114 time: redux::Timestamp,
115 won_slot: BlockProducerWonSlot,
116 chain: Vec<AppliedBlock>,
118 block: BlockWithoutProof,
119 block_hash: v2::StateHash,
120 proof: Arc<v2::MinaBaseProofStableV2>,
121 },
122 Produced {
123 time: redux::Timestamp,
124 won_slot: BlockProducerWonSlot,
125 chain: Vec<AppliedBlock>,
127 block: ArcBlockWithHash,
128 },
129 Injected {
130 time: redux::Timestamp,
131 won_slot: BlockProducerWonSlot,
132 chain: Vec<AppliedBlock>,
134 block: ArcBlockWithHash,
135 },
136}
137
138#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Copy)]
139pub enum BlockProducerWonSlotDiscardReason {
140 BestTipStakingLedgerDifferent,
141 BestTipGlobalSlotHigher,
142 BestTipSuperior,
143}
144
145impl BlockProducerState {
146 pub fn new(now: redux::Timestamp, config: Option<BlockProducerConfig>) -> Self {
147 Self(config.map(|config| BlockProducerEnabled {
148 config: config.clone(),
149 vrf_evaluator: BlockProducerVrfEvaluatorState::new(now),
150 current: BlockProducerCurrentState::Idle { time: now },
151 injected_blocks: Default::default(),
152 }))
153 }
154
155 pub fn with<'a, F, R: 'a>(&'a self, default: R, fun: F) -> R
156 where
157 F: FnOnce(&'a BlockProducerEnabled) -> R,
158 {
159 self.0.as_ref().map_or(default, fun)
160 }
161
162 pub fn as_mut(&mut self) -> Option<&mut BlockProducerEnabled> {
163 self.0.as_mut()
164 }
165
166 pub fn as_ref(&self) -> Option<&BlockProducerEnabled> {
167 self.0.as_ref()
168 }
169
170 pub fn is_enabled(&self) -> bool {
171 self.0.is_some()
172 }
173
174 pub fn config(&self) -> Option<&BlockProducerConfig> {
175 self.with(None, |this| Some(&this.config))
176 }
177
178 pub fn is_me(&self, producer: &v2::NonZeroCurvePoint) -> bool {
179 self.with(false, |this| producer == &this.config.pub_key)
180 }
181
182 pub fn is_produced_by_me(&self, block: &ArcBlockWithHash) -> bool {
184 self.with(false, |this| {
185 block.producer() == &this.config.pub_key && this.injected_blocks.contains(block.hash())
186 })
187 }
188
189 pub fn is_producing(&self) -> bool {
190 self.with(false, |this| this.current.is_producing())
191 }
192
193 pub fn current_won_slot(&self) -> Option<&BlockProducerWonSlot> {
194 self.with(None, |this| this.current.won_slot())
195 }
196
197 pub fn current_parent_chain(&self) -> Option<&[AppliedBlock]> {
198 self.with(None, |this| this.current.parent_chain())
199 }
200
201 pub fn producing_won_slot(&self) -> Option<&BlockProducerWonSlot> {
203 self.current_won_slot().filter(|_| self.is_producing())
204 }
205
206 pub fn produced_block(&self) -> Option<&ArcBlockWithHash> {
207 self.with(None, |this| this.current.produced_block())
208 }
209
210 pub fn produced_block_with_chain(&self) -> Option<(&ArcBlockWithHash, &[AppliedBlock])> {
211 self.with(None, |this| this.current.produced_block_with_chain())
212 }
213
214 pub fn vrf_evaluator(&self) -> Option<&BlockProducerVrfEvaluatorState> {
215 self.with(None, |this| Some(&this.vrf_evaluator))
216 }
217
218 pub fn vrf_evaluator_with_config(
219 &self,
220 ) -> Option<(&BlockProducerVrfEvaluatorState, &BlockProducerConfig)> {
221 self.with(None, |this| Some((&this.vrf_evaluator, &this.config)))
222 }
223
224 pub fn vrf_delegator_table_inputs(&self) -> Option<(&v2::LedgerHash, &AccountPublicKey)> {
226 self.vrf_evaluator()?.vrf_delegator_table_inputs()
227 }
228
229 pub fn pending_transactions(&self) -> Vec<valid::UserCommand> {
230 self.with(Vec::new(), |this| this.current.pending_transactions())
231 }
232}
233
234impl BlockProducerCurrentState {
235 pub fn won_slot_should_search(&self) -> bool {
236 match self {
237 Self::Idle { .. } | Self::WonSlotDiscarded { .. } | Self::Injected { .. } => true,
238 Self::WonSlot { .. }
239 | Self::WonSlotWait { .. }
240 | Self::WonSlotProduceInit { .. }
241 | Self::WonSlotTransactionsGet { .. }
242 | Self::WonSlotTransactionsSuccess { .. }
243 | Self::StagedLedgerDiffCreatePending { .. }
244 | Self::StagedLedgerDiffCreateSuccess { .. }
245 | Self::BlockUnprovenBuilt { .. }
246 | Self::BlockProvePending { .. }
247 | Self::BlockProveSuccess { .. }
248 | Self::Produced { .. } => false,
249 }
250 }
251
252 pub fn won_slot(&self) -> Option<&BlockProducerWonSlot> {
253 match self {
254 Self::Idle { .. } => None,
255 Self::WonSlotDiscarded { won_slot, .. }
256 | Self::WonSlot { won_slot, .. }
257 | Self::WonSlotWait { won_slot, .. }
258 | Self::WonSlotProduceInit { won_slot, .. }
259 | Self::WonSlotTransactionsGet { won_slot, .. }
260 | Self::WonSlotTransactionsSuccess { won_slot, .. }
261 | Self::StagedLedgerDiffCreatePending { won_slot, .. }
262 | Self::StagedLedgerDiffCreateSuccess { won_slot, .. }
263 | Self::BlockUnprovenBuilt { won_slot, .. }
264 | Self::BlockProvePending { won_slot, .. }
265 | Self::BlockProveSuccess { won_slot, .. }
266 | Self::Produced { won_slot, .. }
267 | Self::Injected { won_slot, .. } => Some(won_slot),
268 }
269 }
270
271 pub fn parent_chain(&self) -> Option<&[AppliedBlock]> {
272 match self {
273 Self::Idle { .. }
274 | Self::WonSlotDiscarded { .. }
275 | Self::WonSlot { .. }
276 | Self::WonSlotWait { .. } => None,
277 Self::WonSlotProduceInit { chain, .. }
278 | Self::WonSlotTransactionsGet { chain, .. }
279 | Self::WonSlotTransactionsSuccess { chain, .. }
280 | Self::StagedLedgerDiffCreatePending { chain, .. }
281 | Self::StagedLedgerDiffCreateSuccess { chain, .. }
282 | Self::BlockUnprovenBuilt { chain, .. }
283 | Self::BlockProvePending { chain, .. }
284 | Self::BlockProveSuccess { chain, .. }
285 | Self::Produced { chain, .. }
286 | Self::Injected { chain, .. } => Some(chain),
287 }
288 }
289
290 pub fn won_slot_should_wait(&self, now: redux::Timestamp) -> bool {
291 matches!(self, Self::WonSlot { .. }) && !self.won_slot_should_produce(now)
292 }
293
294 pub fn won_slot_should_produce(&self, now: redux::Timestamp) -> bool {
295 #[cfg(not(target_arch = "wasm32"))]
297 const BLOCK_PRODUCTION_ESTIMATE: u64 = Duration::from_secs(6).as_nanos() as u64;
298 #[cfg(target_arch = "wasm32")]
299 const BLOCK_PRODUCTION_ESTIMATE: u64 = Duration::from_secs(20).as_nanos() as u64;
300
301 let slot_interval = Duration::from_secs(3 * 60).as_nanos() as u64;
302 match self {
303 Self::WonSlot { won_slot, .. } | Self::WonSlotWait { won_slot, .. } => {
304 let slot_upper_bound = won_slot
306 .slot_time
307 .checked_add(slot_interval)
308 .expect("overflow");
309 let estimated_produced_time = now
310 .checked_add(BLOCK_PRODUCTION_ESTIMATE)
311 .expect("overflow");
312 estimated_produced_time >= won_slot.slot_time && now < slot_upper_bound
313 }
314 _ => false,
315 }
316 }
317
318 pub fn won_slot_should_discard(
319 &self,
320 best_tip: &ArcBlockWithHash,
321 ) -> Option<BlockProducerWonSlotDiscardReason> {
322 if matches!(self, Self::WonSlotDiscarded { .. }) {
323 return None;
324 }
325
326 let won_slot = self.won_slot()?;
327 if won_slot.global_slot() < best_tip.global_slot() {
328 return Some(BlockProducerWonSlotDiscardReason::BestTipGlobalSlotHigher);
329 }
330
331 if &won_slot.staking_ledger_hash != best_tip.staking_epoch_ledger_hash()
332 && &won_slot.staking_ledger_hash != best_tip.next_epoch_ledger_hash()
333 {
334 return Some(BlockProducerWonSlotDiscardReason::BestTipStakingLedgerDifferent);
335 }
336
337 if won_slot < best_tip
338 || self.produced_block().is_some_and(|block| {
339 !consensus_take(
340 best_tip.consensus_state(),
341 block.consensus_state(),
342 best_tip.hash(),
343 block.hash(),
344 )
345 })
346 {
347 return Some(BlockProducerWonSlotDiscardReason::BestTipSuperior);
348 }
349
350 None
351 }
352
353 pub fn is_producing(&self) -> bool {
354 match self {
355 Self::Idle { .. }
356 | Self::WonSlotDiscarded { .. }
357 | Self::WonSlot { .. }
358 | Self::WonSlotWait { .. }
359 | Self::Injected { .. } => false,
360 Self::WonSlotProduceInit { .. }
361 | Self::WonSlotTransactionsGet { .. }
362 | Self::WonSlotTransactionsSuccess { .. }
363 | Self::StagedLedgerDiffCreatePending { .. }
364 | Self::StagedLedgerDiffCreateSuccess { .. }
365 | Self::BlockUnprovenBuilt { .. }
366 | Self::BlockProvePending { .. }
367 | Self::BlockProveSuccess { .. }
368 | Self::Produced { .. } => true,
369 }
370 }
371
372 pub fn produced_block(&self) -> Option<&ArcBlockWithHash> {
373 match self {
374 Self::Produced { block, .. } => Some(block),
375 _ => None,
376 }
377 }
378
379 pub fn injected_block(&self) -> Option<&ArcBlockWithHash> {
380 match self {
381 Self::Injected { block, .. } => Some(block),
382 _ => None,
383 }
384 }
385
386 pub fn produced_block_with_chain(&self) -> Option<(&ArcBlockWithHash, &[AppliedBlock])> {
387 match self {
388 Self::Produced { chain, block, .. } => Some((block, chain)),
389 _ => None,
390 }
391 }
392
393 pub fn pending_transactions(&self) -> Vec<valid::UserCommand> {
394 match self {
395 Self::WonSlotTransactionsSuccess {
396 transactions_by_fee,
397 ..
398 }
399 | Self::StagedLedgerDiffCreatePending {
400 transactions_by_fee,
401 ..
402 } => transactions_by_fee.to_vec(),
403 _ => vec![],
404 }
405 }
406}
407
408impl Default for BlockProducerCurrentState {
409 fn default() -> Self {
410 Self::Idle {
411 time: redux::Timestamp::ZERO,
412 }
413 }
414}