Skip to main content

ethexe_common/
primitives.rs

1// Copyright (C) Gear Technologies Inc.
2// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
3
4use crate::events::BlockEvent;
5use alloc::{
6    collections::{btree_map::BTreeMap, btree_set::BTreeSet},
7    vec::Vec,
8};
9use core::num::NonZeroU64;
10use gear_core::ids::prelude::CodeIdExt as _;
11use gprimitives::{ActorId, CodeId, H256, MessageId};
12use parity_scale_codec::{Decode, Encode};
13use scale_info::TypeInfo;
14
15pub type ProgramStates = BTreeMap<ActorId, StateHashWithQueueSize>;
16
17#[derive(Debug, Clone, Copy, Default, Encode, Decode, TypeInfo, PartialEq, Eq, Hash)]
18#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
19pub struct BlockHeader {
20    pub height: u32,
21    pub timestamp: u64,
22    pub parent_hash: H256,
23}
24
25impl BlockHeader {
26    pub fn dummy(height: u32) -> Self {
27        let mut parent_hash = [0; 32];
28        parent_hash[..4].copy_from_slice(&height.to_le_bytes());
29
30        Self {
31            height,
32            timestamp: height as u64 * 12,
33            parent_hash: parent_hash.into(),
34        }
35    }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct BlockData {
40    pub hash: H256,
41    pub header: BlockHeader,
42    pub events: Vec<BlockEvent>,
43}
44
45impl BlockData {
46    pub fn to_simple(&self) -> SimpleBlockData {
47        SimpleBlockData {
48            hash: self.hash,
49            header: self.header,
50        }
51    }
52}
53
54#[derive(
55    Debug, derive_more::Display, Copy, Clone, PartialEq, Eq, Encode, Decode, TypeInfo, Default,
56)]
57#[display("Block(hash: {hash}, height: {}, parent: {}, ts: {})", header.height, header.parent_hash, header.timestamp)]
58pub struct SimpleBlockData {
59    pub hash: H256,
60    pub header: BlockHeader,
61}
62
63/// [`PromisePolicy`] tells processor whether should it emits promises or not.
64#[derive(Clone, Debug, Copy, Default, PartialEq, Eq, Encode, Decode, derive_more::IsVariant)]
65pub enum PromisePolicy {
66    /// Emits promises in execution process.
67    Enabled,
68    // Do not emit promises in execution process.
69    #[default]
70    Disabled,
71}
72
73/// The [PromiseEmissionMode] configures the promise emission mode for the ethexe node
74#[derive(Debug, Copy, Clone, PartialEq, Eq, derive_more::IsVariant, Default)]
75pub enum PromiseEmissionMode {
76    /// Node should always emit promises during MB execution.
77    /// Always set [`PromisePolicy::Enabled`].
78    AlwaysEmit,
79    /// [`PromisePolicy`] is decided per-MB by the consensus / compute layer.
80    #[default]
81    ConsensusDriven,
82}
83
84#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, Default, Encode, Decode, TypeInfo)]
85#[cfg_attr(feature = "std", derive(serde::Serialize))]
86pub struct StateHashWithQueueSize {
87    pub hash: H256,
88    pub canonical_queue_size: u8,
89    pub injected_queue_size: u8,
90}
91
92impl StateHashWithQueueSize {
93    pub fn zero() -> Self {
94        Self {
95            hash: H256::zero(),
96            canonical_queue_size: 0,
97            injected_queue_size: 0,
98        }
99    }
100}
101
102#[derive(Debug, Clone, Default, Encode, Decode, TypeInfo, PartialEq, Eq)]
103pub struct CodeBlobInfo {
104    pub timestamp: u64,
105    pub tx_hash: H256,
106}
107
108#[derive(Clone, PartialEq, Eq, derive_more::Debug)]
109pub struct CodeAndIdUnchecked {
110    #[debug("{:#x} bytes", code.len())]
111    pub code: Vec<u8>,
112    pub code_id: CodeId,
113}
114
115#[derive(Clone, PartialEq, Eq, derive_more::Debug)]
116pub struct CodeAndId {
117    #[debug("{:#x} bytes", code.len())]
118    code: Vec<u8>,
119    code_id: CodeId,
120}
121
122impl CodeAndId {
123    pub fn new(code: Vec<u8>) -> Self {
124        let code_id = CodeId::generate(&code);
125        Self { code, code_id }
126    }
127
128    pub fn code(&self) -> &[u8] {
129        &self.code
130    }
131
132    pub fn code_id(&self) -> CodeId {
133        self.code_id
134    }
135
136    /// Creates a new `CodeAndId` from an unchecked version, asserting that the `code_id` matches the generated one.
137    /// # Panics
138    ///
139    /// If the `code_id` does not match the generated one from the `code`, this function will panic.
140    pub fn from_unchecked(code_and_id: CodeAndIdUnchecked) -> Self {
141        let CodeAndIdUnchecked { code, code_id } = code_and_id;
142        assert_eq!(
143            code_id,
144            CodeId::generate(&code),
145            "CodeId does not match the provided code"
146        );
147        Self { code, code_id }
148    }
149
150    pub fn into_unchecked(self) -> CodeAndIdUnchecked {
151        CodeAndIdUnchecked {
152            code: self.code,
153            code_id: self.code_id,
154        }
155    }
156}
157
158/// GearExe network timelines configuration. Parameters fetched the Router contract.
159/// This struct stores in the database, because of using in the multiple places.
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, TypeInfo)]
161pub struct ProtocolTimelines {
162    // The genesis timestamp of the GearExe network in seconds.
163    pub genesis_ts: u64,
164    // The duration of an era in seconds.
165    pub era: NonZeroU64,
166    /// The election duration in seconds before the end of an era when the next set of validators elected.
167    ///  (start of era)[ - - - - - - - - - - - - + - - - - ] (end of era)
168    ///                                          ^ election
169    pub election: u64,
170    /// The slot duration in seconds.
171    pub slot: NonZeroU64,
172}
173
174impl ProtocolTimelines {
175    /// Returns the era index for the given timestamp. Eras starts from 0.
176    ///
177    /// Returns `None` if `ts < genesis_ts`
178    #[inline(always)]
179    pub fn era_from_ts(&self, ts: u64) -> Option<u64> {
180        ts.checked_sub(self.genesis_ts)
181            .map(|delta| delta / self.era.get())
182    }
183
184    /// Returns the timestamp since which the given era started.
185    ///
186    /// Returns `None` if overflows u64.
187    #[inline(always)]
188    pub fn era_start_ts(&self, era_index: u64) -> Option<u64> {
189        era_index
190            .checked_mul(self.era.get())?
191            .checked_add(self.genesis_ts)
192    }
193
194    /// Returns the timestamp when election starts in the given era.
195    /// NOTE: election starts for the next era validators.
196    ///
197    /// Returns `None` if overflows u64.
198    ///
199    /// # Panics
200    /// Panics if `era duration < election duration`
201    #[inline(always)]
202    pub fn era_election_start_ts(&self, era_index: u64) -> Option<u64> {
203        self.era_start_ts(era_index)?.checked_add(
204            self.era
205                .get()
206                .checked_sub(self.election)
207                .expect("Incorrect Timelines - era duration < election duration"),
208        )
209    }
210
211    /// Returns the slot index for the given timestamp. Slots starts from 0.
212    ///
213    /// Returns `None` if `ts < genesis_ts`
214    #[inline(always)]
215    pub fn slot_from_ts(&self, ts: u64) -> Option<u64> {
216        ts.checked_sub(self.genesis_ts)
217            .map(|delta| delta / self.slot.get())
218    }
219}
220
221/// RemoveFromMailbox key; (msgs sources program (mailbox and queue provider), destination user id)
222pub type Rfm = (ActorId, ActorId);
223
224/// SendDispatch key; (msgs destinations program (stash and queue provider), message id)
225pub type Sd = (ActorId, MessageId);
226
227/// SendUserMessage key; (msgs sources program (mailbox and stash provider))
228pub type Sum = ActorId;
229
230/// NOTE: generic keys differs to Vara and have been chosen dependent on storage organization of ethexe.
231pub type ScheduledTask = gear_core::tasks::ScheduledTask<Rfm, Sd, Sum>;
232
233/// Scheduler; (block height, scheduled task)
234pub type Schedule = BTreeMap<u32, BTreeSet<ScheduledTask>>;
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239
240    fn mock_timelines() -> ProtocolTimelines {
241        ProtocolTimelines {
242            genesis_ts: 10,
243            era: NonZeroU64::new(234).unwrap(),
244            election: 200,
245            slot: NonZeroU64::new(10).unwrap(),
246        }
247    }
248
249    #[test]
250    fn test_era_from_ts_calculation() {
251        let timelines = mock_timelines();
252
253        // For 0 era
254        assert_eq!(timelines.era_from_ts(10), Some(0));
255        assert_eq!(timelines.era_from_ts(45), Some(0));
256        assert_eq!(timelines.era_from_ts(243), Some(0));
257
258        // For 1 era
259        assert_eq!(timelines.era_from_ts(244), Some(1));
260        assert_eq!(timelines.era_from_ts(333), Some(1));
261    }
262
263    #[test]
264    fn era_from_ts_returns_none_before_genesis() {
265        let result = ProtocolTimelines {
266            genesis_ts: 100,
267            ..mock_timelines()
268        }
269        .era_from_ts(50);
270        assert_eq!(result, None);
271    }
272
273    #[test]
274    fn test_era_start_calculation() {
275        let timelines = mock_timelines();
276
277        // For 0 era
278        assert_eq!(timelines.era_start_ts(0), Some(10));
279        assert_eq!(timelines.era_start_ts(0), Some(10));
280        assert_eq!(timelines.era_start_ts(0), Some(10));
281
282        // For 1 era
283        assert_eq!(timelines.era_start_ts(1), Some(244));
284        assert_eq!(timelines.era_start_ts(1), Some(244));
285    }
286}