Skip to main content

ethexe_common/
consensus.rs

1// Copyright (C) Gear Technologies Inc.
2// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
3
4use crate::{
5    Address, Digest, ProtocolTimelines, ToDigest,
6    ecdsa::{ContractSignature, VerifiedData},
7    gear::BatchCommitment,
8    validators::ValidatorsVec,
9};
10use alloc::vec::Vec;
11use core::num::NonZeroUsize;
12use gprimitives::{CodeId, H256};
13use k256::sha2::Digest as _;
14use parity_scale_codec::{Decode, Encode};
15use sha3::Keccak256;
16
17/// The maximum batch size limit - 120 KB.
18pub const MAX_BATCH_SIZE_LIMIT: u64 = 120 * 1024;
19
20/// The default batch size - 100 KB.
21pub const DEFAULT_BATCH_SIZE_LIMIT: u64 = 100 * 1024;
22
23pub type VerifiedValidationRequest = VerifiedData<BatchCommitmentValidationRequest>;
24pub type VerifiedValidationReply = VerifiedData<BatchCommitmentValidationReply>;
25
26// TODO #4553: temporary implementation, should be improved
27/// Returns batch coordinator index for time slot. Next slot is the next validator in the list.
28pub const fn block_coordinator_index_for_slot(validators_amount: NonZeroUsize, slot: u64) -> usize {
29    (slot % validators_amount.get() as u64) as usize
30}
31
32impl ProtocolTimelines {
33    /// Calculates the coordinator address for a given Ethereum block timestamp.
34    ///
35    /// The coordinator is the validator picked once per Ethereum block to
36    /// aggregate finalized MBs into a [`BatchCommitment`] and submit it
37    /// on-chain. Block production itself is driven by Malachite — coordinator
38    /// election is independent.
39    ///
40    /// # Arguments
41    /// * `validators` - A non-empty vector of validator addresses.
42    /// * `timestamp` - The timestamp for which to calculate the coordinator.
43    ///
44    /// Returns `None` if timestamp is before genesis.
45    pub fn block_coordinator_at(
46        &self,
47        validators: &ValidatorsVec,
48        timestamp: u64,
49    ) -> Option<Address> {
50        let idx = self.block_coordinator_index_at(validators.len_nonzero(), timestamp)?;
51        validators.get(idx).cloned()
52    }
53
54    /// Calculates the coordinator index for a given Ethereum block timestamp.
55    ///
56    /// # Arguments
57    /// * `validators_amount` - The number of validators in the protocol.
58    /// * `timestamp` - The timestamp for which to calculate the coordinator index.
59    ///
60    /// Returns `None` if timestamp is before genesis.
61    pub fn block_coordinator_index_at(
62        &self,
63        validators_amount: NonZeroUsize,
64        timestamp: u64,
65    ) -> Option<usize> {
66        let slot = self.slot_from_ts(timestamp)?;
67        Some(block_coordinator_index_for_slot(validators_amount, slot))
68    }
69}
70
71/// Represents a request for validating a batch commitment.
72#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq, Hash)]
73pub struct BatchCommitmentValidationRequest {
74    // Digest of batch commitment to validate
75    pub digest: Digest,
76    /// Optional head MB hash of the chain commitment.
77    /// The hash of the most recent finalized `ethexe_malachite_core::Block` envelope covered by this batch.
78    pub head: Option<H256>,
79    /// List of codes which are part of the batch
80    pub codes: Vec<CodeId>,
81    /// Whether rewards commitment is part of the batch
82    pub rewards: bool,
83    /// Whether validators commitment is part of the batch
84    pub validators: bool,
85}
86
87impl BatchCommitmentValidationRequest {
88    pub fn new(batch: &BatchCommitment) -> Self {
89        let codes = batch
90            .code_commitments
91            .iter()
92            .map(|commitment| commitment.id)
93            .collect();
94
95        BatchCommitmentValidationRequest {
96            digest: batch.to_digest(),
97            head: batch.chain_commitment.as_ref().map(|cc| cc.head),
98            codes,
99            rewards: batch.rewards_commitment.is_some(),
100            validators: batch.validators_commitment.is_some(),
101        }
102    }
103}
104
105impl ToDigest for BatchCommitmentValidationRequest {
106    fn update_hasher(&self, hasher: &mut sha3::Keccak256) {
107        let Self {
108            digest,
109            head,
110            codes,
111            rewards,
112            validators,
113        } = self;
114
115        hasher.update(digest);
116        head.map(|h| hasher.update(h.0));
117        hasher.update(
118            codes
119                .iter()
120                .flat_map(|h| h.into_bytes())
121                .collect::<Vec<u8>>(),
122        );
123        hasher.update([*rewards as u8]);
124        hasher.update([*validators as u8]);
125    }
126}
127
128/// A reply to a batch commitment validation request.
129/// Contains the digest of the batch and a signature confirming the validation.
130#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq, Hash)]
131pub struct BatchCommitmentValidationReply {
132    /// Digest of the [`BatchCommitment`] being validated
133    pub digest: Digest,
134    /// Signature confirming the validation by origin
135    pub signature: ContractSignature,
136}
137
138impl ToDigest for BatchCommitmentValidationReply {
139    fn update_hasher(&self, hasher: &mut Keccak256) {
140        let Self { digest, signature } = self;
141        hasher.update(digest.0);
142        hasher.update(signature.into_pre_eip155_bytes())
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use core::num::NonZeroU64;
150
151    #[test]
152    fn block_coordinator_index_calculates_correct_index() {
153        let validators_amount = NonZeroUsize::new(5).unwrap();
154        let slot = 7;
155
156        let index = block_coordinator_index_for_slot(validators_amount, slot);
157
158        assert_eq!(index, 2);
159    }
160
161    #[test]
162    fn block_coordinator_for_calculates_correct_coordinator() {
163        let validators: ValidatorsVec = vec![
164            Address::from([1; 20]),
165            Address::from([2; 20]),
166            Address::from([3; 20]),
167        ]
168        .try_into()
169        .unwrap();
170
171        let coordinator = ProtocolTimelines {
172            slot: NonZeroU64::new(1).unwrap(),
173            genesis_ts: 0,
174            era: NonZeroU64::new(1).unwrap(),
175            election: 0,
176        }
177        .block_coordinator_at(&validators, 10);
178
179        assert_eq!(coordinator, Some(Address::from([2; 20])));
180    }
181
182    #[test]
183    fn block_coordinator_for_calculates_correct_coordinator_with_genesis_timestamp() {
184        let validators: ValidatorsVec = vec![
185            Address::from([1; 20]),
186            Address::from([2; 20]),
187            Address::from([3; 20]),
188        ]
189        .try_into()
190        .unwrap();
191
192        let coordinator = ProtocolTimelines {
193            slot: NonZeroU64::new(2).unwrap(),
194            genesis_ts: 6,
195            era: NonZeroU64::new(1).unwrap(),
196            election: 0,
197        }
198        .block_coordinator_at(&validators, 16);
199
200        assert_eq!(coordinator, Some(Address::from([3; 20])));
201    }
202
203    #[test]
204    fn block_coordinator_at_returns_none_before_genesis() {
205        let validators: ValidatorsVec = vec![Address::from([1; 20])].try_into().unwrap();
206
207        let coordinator = ProtocolTimelines {
208            slot: NonZeroU64::new(1).unwrap(),
209            genesis_ts: 100,
210            era: NonZeroU64::new(1).unwrap(),
211            election: 0,
212        }
213        .block_coordinator_at(&validators, 50);
214
215        assert_eq!(coordinator, None);
216    }
217}