1use std::{collections::BTreeSet, sync::Arc, time::Duration};
2
3use ledger::scan_state::transaction_logic::valid;
4use mina_core::{
5 block::{AppliedBlock, ArcBlockWithHash},
6 consensus::consensus_take,
7};
8use mina_p2p_messages::v2;
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)]
139#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
140pub enum BlockProducerWonSlotDiscardReason {
141 BestTipStakingLedgerDifferent,
142 BestTipGlobalSlotHigher,
143 BestTipSuperior,
144}
145
146impl BlockProducerState {
147 pub fn new(now: redux::Timestamp, config: Option<BlockProducerConfig>) -> Self {
148 Self(config.map(|config| BlockProducerEnabled {
149 config: config.clone(),
150 vrf_evaluator: BlockProducerVrfEvaluatorState::new(now),
151 current: BlockProducerCurrentState::Idle { time: now },
152 injected_blocks: Default::default(),
153 }))
154 }
155
156 pub fn with<'a, F, R: 'a>(&'a self, default: R, fun: F) -> R
157 where
158 F: FnOnce(&'a BlockProducerEnabled) -> R,
159 {
160 self.0.as_ref().map_or(default, fun)
161 }
162
163 pub fn as_mut(&mut self) -> Option<&mut BlockProducerEnabled> {
164 self.0.as_mut()
165 }
166
167 pub fn as_ref(&self) -> Option<&BlockProducerEnabled> {
168 self.0.as_ref()
169 }
170
171 pub fn is_enabled(&self) -> bool {
172 self.0.is_some()
173 }
174
175 pub fn config(&self) -> Option<&BlockProducerConfig> {
176 self.with(None, |this| Some(&this.config))
177 }
178
179 pub fn is_me(&self, producer: &v2::NonZeroCurvePoint) -> bool {
180 self.with(false, |this| producer == &this.config.pub_key)
181 }
182
183 pub fn is_produced_by_me(&self, block: &ArcBlockWithHash) -> bool {
185 self.with(false, |this| {
186 block.producer() == &this.config.pub_key && this.injected_blocks.contains(block.hash())
187 })
188 }
189
190 pub fn is_producing(&self) -> bool {
191 self.with(false, |this| this.current.is_producing())
192 }
193
194 pub fn current_won_slot(&self) -> Option<&BlockProducerWonSlot> {
195 self.with(None, |this| this.current.won_slot())
196 }
197
198 pub fn current_parent_chain(&self) -> Option<&[AppliedBlock]> {
199 self.with(None, |this| this.current.parent_chain())
200 }
201
202 pub fn producing_won_slot(&self) -> Option<&BlockProducerWonSlot> {
204 self.current_won_slot().filter(|_| self.is_producing())
205 }
206
207 pub fn produced_block(&self) -> Option<&ArcBlockWithHash> {
208 self.with(None, |this| this.current.produced_block())
209 }
210
211 pub fn produced_block_with_chain(&self) -> Option<(&ArcBlockWithHash, &[AppliedBlock])> {
212 self.with(None, |this| this.current.produced_block_with_chain())
213 }
214
215 pub fn vrf_evaluator(&self) -> Option<&BlockProducerVrfEvaluatorState> {
216 self.with(None, |this| Some(&this.vrf_evaluator))
217 }
218
219 pub fn vrf_evaluator_with_config(
220 &self,
221 ) -> Option<(&BlockProducerVrfEvaluatorState, &BlockProducerConfig)> {
222 self.with(None, |this| Some((&this.vrf_evaluator, &this.config)))
223 }
224
225 pub fn vrf_delegator_table_inputs(&self) -> Option<(&v2::LedgerHash, &AccountPublicKey)> {
227 self.vrf_evaluator()?.vrf_delegator_table_inputs()
228 }
229
230 pub fn pending_transactions(&self) -> Vec<valid::UserCommand> {
231 self.with(Vec::new(), |this| this.current.pending_transactions())
232 }
233}
234
235impl BlockProducerCurrentState {
236 pub fn won_slot_should_search(&self) -> bool {
237 match self {
238 Self::Idle { .. } | Self::WonSlotDiscarded { .. } | Self::Injected { .. } => true,
239 Self::WonSlot { .. }
240 | Self::WonSlotWait { .. }
241 | Self::WonSlotProduceInit { .. }
242 | Self::WonSlotTransactionsGet { .. }
243 | Self::WonSlotTransactionsSuccess { .. }
244 | Self::StagedLedgerDiffCreatePending { .. }
245 | Self::StagedLedgerDiffCreateSuccess { .. }
246 | Self::BlockUnprovenBuilt { .. }
247 | Self::BlockProvePending { .. }
248 | Self::BlockProveSuccess { .. }
249 | Self::Produced { .. } => false,
250 }
251 }
252
253 pub fn won_slot(&self) -> Option<&BlockProducerWonSlot> {
254 match self {
255 Self::Idle { .. } => None,
256 Self::WonSlotDiscarded { won_slot, .. }
257 | Self::WonSlot { won_slot, .. }
258 | Self::WonSlotWait { won_slot, .. }
259 | Self::WonSlotProduceInit { won_slot, .. }
260 | Self::WonSlotTransactionsGet { won_slot, .. }
261 | Self::WonSlotTransactionsSuccess { won_slot, .. }
262 | Self::StagedLedgerDiffCreatePending { won_slot, .. }
263 | Self::StagedLedgerDiffCreateSuccess { won_slot, .. }
264 | Self::BlockUnprovenBuilt { won_slot, .. }
265 | Self::BlockProvePending { won_slot, .. }
266 | Self::BlockProveSuccess { won_slot, .. }
267 | Self::Produced { won_slot, .. }
268 | Self::Injected { won_slot, .. } => Some(won_slot),
269 }
270 }
271
272 pub fn parent_chain(&self) -> Option<&[AppliedBlock]> {
273 match self {
274 Self::Idle { .. }
275 | Self::WonSlotDiscarded { .. }
276 | Self::WonSlot { .. }
277 | Self::WonSlotWait { .. } => None,
278 Self::WonSlotProduceInit { chain, .. }
279 | Self::WonSlotTransactionsGet { chain, .. }
280 | Self::WonSlotTransactionsSuccess { chain, .. }
281 | Self::StagedLedgerDiffCreatePending { chain, .. }
282 | Self::StagedLedgerDiffCreateSuccess { chain, .. }
283 | Self::BlockUnprovenBuilt { chain, .. }
284 | Self::BlockProvePending { chain, .. }
285 | Self::BlockProveSuccess { chain, .. }
286 | Self::Produced { chain, .. }
287 | Self::Injected { chain, .. } => Some(chain),
288 }
289 }
290
291 pub fn won_slot_should_wait(&self, now: redux::Timestamp) -> bool {
292 matches!(self, Self::WonSlot { .. }) && !self.won_slot_should_produce(now)
293 }
294
295 pub fn won_slot_should_produce(&self, now: redux::Timestamp) -> bool {
296 #[cfg(not(target_arch = "wasm32"))]
298 const BLOCK_PRODUCTION_ESTIMATE: u64 = Duration::from_secs(6).as_nanos() as u64;
299 #[cfg(target_arch = "wasm32")]
300 const BLOCK_PRODUCTION_ESTIMATE: u64 = Duration::from_secs(20).as_nanos() as u64;
301
302 let slot_interval = Duration::from_secs(3 * 60).as_nanos() as u64;
303 match self {
304 Self::WonSlot { won_slot, .. } | Self::WonSlotWait { won_slot, .. } => {
305 let slot_upper_bound = won_slot
307 .slot_time
308 .checked_add(slot_interval)
309 .expect("overflow");
310 let estimated_produced_time = now
311 .checked_add(BLOCK_PRODUCTION_ESTIMATE)
312 .expect("overflow");
313 estimated_produced_time >= won_slot.slot_time && now < slot_upper_bound
314 }
315 _ => false,
316 }
317 }
318
319 pub fn won_slot_should_discard(
320 &self,
321 best_tip: &ArcBlockWithHash,
322 ) -> Option<BlockProducerWonSlotDiscardReason> {
323 if matches!(self, Self::WonSlotDiscarded { .. }) {
324 return None;
325 }
326
327 let won_slot = self.won_slot()?;
328 if won_slot.global_slot() < best_tip.global_slot() {
329 return Some(BlockProducerWonSlotDiscardReason::BestTipGlobalSlotHigher);
330 }
331
332 if &won_slot.staking_ledger_hash != best_tip.staking_epoch_ledger_hash()
333 && &won_slot.staking_ledger_hash != best_tip.next_epoch_ledger_hash()
334 {
335 return Some(BlockProducerWonSlotDiscardReason::BestTipStakingLedgerDifferent);
336 }
337
338 if won_slot < best_tip
339 || self.produced_block().is_some_and(|block| {
340 !consensus_take(
341 best_tip.consensus_state(),
342 block.consensus_state(),
343 best_tip.hash(),
344 block.hash(),
345 )
346 })
347 {
348 return Some(BlockProducerWonSlotDiscardReason::BestTipSuperior);
349 }
350
351 None
352 }
353
354 pub fn is_producing(&self) -> bool {
355 match self {
356 Self::Idle { .. }
357 | Self::WonSlotDiscarded { .. }
358 | Self::WonSlot { .. }
359 | Self::WonSlotWait { .. }
360 | Self::Injected { .. } => false,
361 Self::WonSlotProduceInit { .. }
362 | Self::WonSlotTransactionsGet { .. }
363 | Self::WonSlotTransactionsSuccess { .. }
364 | Self::StagedLedgerDiffCreatePending { .. }
365 | Self::StagedLedgerDiffCreateSuccess { .. }
366 | Self::BlockUnprovenBuilt { .. }
367 | Self::BlockProvePending { .. }
368 | Self::BlockProveSuccess { .. }
369 | Self::Produced { .. } => true,
370 }
371 }
372
373 pub fn produced_block(&self) -> Option<&ArcBlockWithHash> {
374 match self {
375 Self::Produced { block, .. } => Some(block),
376 _ => None,
377 }
378 }
379
380 pub fn injected_block(&self) -> Option<&ArcBlockWithHash> {
381 match self {
382 Self::Injected { block, .. } => Some(block),
383 _ => None,
384 }
385 }
386
387 pub fn produced_block_with_chain(&self) -> Option<(&ArcBlockWithHash, &[AppliedBlock])> {
388 match self {
389 Self::Produced { chain, block, .. } => Some((block, chain)),
390 _ => None,
391 }
392 }
393
394 pub fn pending_transactions(&self) -> Vec<valid::UserCommand> {
395 match self {
396 Self::WonSlotTransactionsSuccess {
397 transactions_by_fee,
398 ..
399 }
400 | Self::StagedLedgerDiffCreatePending {
401 transactions_by_fee,
402 ..
403 } => transactions_by_fee.to_vec(),
404 _ => vec![],
405 }
406 }
407}
408
409impl Default for BlockProducerCurrentState {
410 fn default() -> Self {
411 Self::Idle {
412 time: redux::Timestamp::ZERO,
413 }
414 }
415}