Skip to main content

mina_node/block_producer/
block_producer_state.rs

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    /// Blocks that were injected into transition frontier, but hasn't
27    /// become our best tip yet.
28    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 that we are extending.
53        chain: Vec<AppliedBlock>,
54    },
55    WonSlotTransactionsGet {
56        time: redux::Timestamp,
57        won_slot: BlockProducerWonSlot,
58        /// Chain that we are extending.
59        chain: Vec<AppliedBlock>,
60    },
61    WonSlotTransactionsSuccess {
62        time: redux::Timestamp,
63        won_slot: BlockProducerWonSlot,
64        /// Chain that we are extending.
65        chain: Vec<AppliedBlock>,
66        transactions_by_fee: Vec<valid::UserCommand>,
67    },
68    StagedLedgerDiffCreatePending {
69        time: redux::Timestamp,
70        won_slot: BlockProducerWonSlot,
71        /// Chain that we are extending.
72        chain: Vec<AppliedBlock>,
73        transactions_by_fee: Vec<valid::UserCommand>,
74    },
75    StagedLedgerDiffCreateSuccess {
76        time: redux::Timestamp,
77        won_slot: BlockProducerWonSlot,
78        /// Chain that we are extending.
79        chain: Vec<AppliedBlock>,
80        diff: v2::StagedLedgerDiffDiffStableV2,
81        /// `protocol_state.blockchain_state.body_reference`
82        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 that we are extending.
93        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 that we are extending.
105        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 that we are extending.
117        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 that we are extending.
126        chain: Vec<AppliedBlock>,
127        block: ArcBlockWithHash,
128    },
129    Injected {
130        time: redux::Timestamp,
131        won_slot: BlockProducerWonSlot,
132        /// Chain that we are extending.
133        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    /// Checks if the block was produced by us recently.
184    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    /// Won slot that we are in the middle of producing.
203    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    /// If we need to construct delegator table, get it's inputs.
226    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        // TODO(binier): maybe have runtime estimate
297        #[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                // Make sure to only producer blocks when in the slot interval
306                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}