node/block_producer/
mod.rs

1pub mod vrf_evaluator;
2
3mod block_producer_config;
4use std::sync::Arc;
5
6pub use block_producer_config::*;
7
8mod block_producer_state;
9pub use block_producer_state::*;
10
11mod block_producer_event;
12pub use block_producer_event::*;
13
14mod block_producer_actions;
15pub use block_producer_actions::*;
16
17mod block_producer_reducer;
18
19use ledger::AccountIndex;
20use mina_p2p_messages::{list::List, v2};
21use openmina_core::{block::ArcBlockWithHash, constants::constraint_constants};
22use poseidon::hash::params::MINA_EPOCH_SEED;
23use serde::{Deserialize, Serialize};
24use vrf::output::VrfOutput;
25
26use self::vrf_evaluator::VrfWonSlotWithHash;
27
28#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
29pub struct BlockProducerWonSlot {
30    pub slot_time: redux::Timestamp,
31    pub delegator: (v2::NonZeroCurvePoint, AccountIndex),
32    pub global_slot: v2::ConsensusGlobalSlotStableV1,
33    pub vrf_output: Box<VrfOutput>,
34    pub value_with_threshold: Option<(f64, f64)>,
35    // Staking ledger which was used during vrf evaluation.
36    pub staking_ledger_hash: v2::LedgerHash,
37}
38
39#[derive(Serialize, Deserialize, Debug, Clone)]
40pub struct BlockWithoutProof {
41    pub protocol_state: v2::MinaStateProtocolStateValueStableV2,
42    pub delta_block_chain_proof: (v2::StateHash, List<v2::StateBodyHash>),
43    pub current_protocol_version: v2::ProtocolVersionStableV2,
44    pub proposed_protocol_version_opt: Option<v2::ProtocolVersionStableV2>,
45    pub body: v2::StagedLedgerDiffBodyStableV1,
46}
47
48impl BlockProducerWonSlot {
49    pub fn from_vrf_won_slot(
50        won_slot_with_hash: &VrfWonSlotWithHash,
51        genesis_timestamp: redux::Timestamp,
52    ) -> Self {
53        let VrfWonSlotWithHash {
54            won_slot,
55            staking_ledger_hash,
56        } = won_slot_with_hash;
57
58        let slot_time = Self::calculate_slot_time(genesis_timestamp, won_slot.global_slot);
59
60        let delegator = (
61            won_slot.winner_account.clone().into(),
62            won_slot.account_index,
63        );
64        let global_slot = v2::ConsensusGlobalSlotStableV1 {
65            slot_number: v2::MinaNumbersGlobalSlotSinceHardForkMStableV1::SinceHardFork(
66                won_slot.global_slot.into(),
67            ),
68            slots_per_epoch: 7140.into(), // TODO
69        };
70
71        Self {
72            slot_time,
73            delegator,
74            global_slot,
75            vrf_output: won_slot.vrf_output.clone(),
76            value_with_threshold: won_slot.value_with_threshold,
77            staking_ledger_hash: staking_ledger_hash.clone(),
78        }
79    }
80
81    fn calculate_slot_time(genesis_timestamp: redux::Timestamp, slot: u32) -> redux::Timestamp {
82        let per_block_ns = constraint_constants()
83            .block_window_duration_ms
84            .saturating_mul(1_000_000);
85        genesis_timestamp
86            .checked_add((slot as u64).checked_mul(per_block_ns).expect("overflow"))
87            .expect("overflow")
88    }
89
90    pub fn global_slot(&self) -> u32 {
91        self.global_slot.slot_number.as_u32()
92    }
93
94    pub fn epoch(&self) -> u32 {
95        self.global_slot()
96            .checked_div(self.global_slot.slots_per_epoch.as_u32())
97            .expect("division by 0")
98    }
99
100    pub fn global_slot_since_genesis(
101        &self,
102        slot_diff: u32,
103    ) -> v2::MinaNumbersGlobalSlotSinceGenesisMStableV1 {
104        let slot = self.global_slot().checked_add(slot_diff).expect("overflow");
105        v2::MinaNumbersGlobalSlotSinceGenesisMStableV1::SinceGenesis(slot.into())
106    }
107
108    pub fn timestamp(&self) -> v2::BlockTimeTimeStableV1 {
109        let ms = u64::from(self.slot_time) / 1_000_000;
110        v2::BlockTimeTimeStableV1(v2::UnsignedExtendedUInt64Int64ForVersionTagsStableV1(
111            ms.into(),
112        ))
113    }
114
115    pub fn next_slot_time(&self) -> redux::Timestamp {
116        self.slot_time
117            .checked_add(3u64.saturating_mul(60).saturating_mul(1_000_000_000_u64))
118            .expect("overflow")
119    }
120}
121
122impl PartialOrd for BlockProducerWonSlot {
123    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
124        Some(self.global_slot().cmp(&other.global_slot()).then_with(|| {
125            v2::ConsensusVrfOutputTruncatedStableV1::from(&*self.vrf_output)
126                .blake2b()
127                .cmp(&v2::ConsensusVrfOutputTruncatedStableV1::from(&*other.vrf_output).blake2b())
128        }))
129    }
130}
131
132impl PartialEq<ArcBlockWithHash> for BlockProducerWonSlot {
133    fn eq(&self, other: &ArcBlockWithHash) -> bool {
134        self.partial_cmp(other).is_some_and(|ord| ord.is_eq())
135    }
136}
137
138impl PartialOrd<ArcBlockWithHash> for BlockProducerWonSlot {
139    fn partial_cmp(&self, other: &ArcBlockWithHash) -> Option<std::cmp::Ordering> {
140        // TODO(binier): this assumes short range fork
141        Some(self.global_slot().cmp(&other.global_slot()).then_with(|| {
142            v2::ConsensusVrfOutputTruncatedStableV1::from(&*self.vrf_output)
143                .blake2b()
144                .cmp(
145                    &other
146                        .header()
147                        .protocol_state
148                        .body
149                        .consensus_state
150                        .last_vrf_output
151                        .blake2b(),
152                )
153        }))
154    }
155}
156
157pub fn to_epoch_and_slot(global_slot: &v2::ConsensusGlobalSlotStableV1) -> (u32, u32) {
158    let epoch = global_slot
159        .slot_number
160        .as_u32()
161        .checked_div(global_slot.slots_per_epoch.as_u32())
162        .expect("division by 0");
163    let slot = global_slot
164        .slot_number
165        .as_u32()
166        .checked_rem(global_slot.slots_per_epoch.as_u32())
167        .expect("division by 0");
168    (epoch, slot)
169}
170
171pub fn next_epoch_first_slot(global_slot: &v2::ConsensusGlobalSlotStableV1) -> u32 {
172    let (epoch, _) = to_epoch_and_slot(global_slot);
173    (epoch.saturating_add(1))
174        .checked_mul(global_slot.slots_per_epoch.as_u32())
175        .expect("overflow")
176}
177
178// Returns the epoch number and whether it is the last slot of the epoch
179// pub fn epoch_with_bounds(global_slot: u32) -> (u32, bool) {
180//     // let epoch_bound = |global_slot| -> (u32, bool) {
181//     //     (global_slot / SLOTS_PER_EPOCH, (global_slot + 1) % SLOTS_PER_EPOCH == 0)
182//     // };
183
184// }
185
186impl BlockWithoutProof {
187    pub fn with_hash_and_proof(
188        self,
189        hash: v2::StateHash,
190        proof: Arc<v2::MinaBaseProofStableV2>,
191    ) -> ArcBlockWithHash {
192        let block = v2::MinaBlockBlockStableV2 {
193            header: v2::MinaBlockHeaderStableV2 {
194                protocol_state: self.protocol_state,
195                protocol_state_proof: proof,
196                delta_block_chain_proof: self.delta_block_chain_proof,
197                current_protocol_version: self.current_protocol_version,
198                proposed_protocol_version_opt: self.proposed_protocol_version_opt,
199            },
200            body: self.body,
201        };
202
203        ArcBlockWithHash {
204            block: block.into(),
205            hash,
206        }
207    }
208}
209
210pub fn calc_epoch_seed(
211    prev_epoch_seed: &v2::EpochSeed,
212    vrf_hash: mina_curves::pasta::Fp,
213) -> v2::EpochSeed {
214    // TODO(adonagy): fix this unwrap
215    let old_seed = prev_epoch_seed.to_field().unwrap();
216    let new_seed = poseidon::hash::hash_with_kimchi(&MINA_EPOCH_SEED, &[old_seed, vrf_hash]);
217    v2::MinaBaseEpochSeedStableV1(new_seed.into()).into()
218}