openmina_core/
chain_id.rs

1use mina_p2p_messages::v2::{
2    MinaBaseProtocolConstantsCheckedValueStableV1, StateHash, UnsignedExtendedUInt32StableV1,
3};
4use multihash::{Blake2b256, Hasher};
5use time::{macros::format_description, OffsetDateTime};
6
7use std::{
8    fmt::{self, Debug, Display, Formatter},
9    io::{Read, Write},
10};
11
12use binprot::{BinProtRead, BinProtWrite};
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14
15#[derive(Clone, PartialEq, Eq)]
16pub struct ChainId([u8; 32]);
17
18fn md5_hash(data: u8) -> String {
19    let mut hasher = md5::Context::new();
20    hasher.consume(data.to_string().as_bytes());
21    let hash: Md5 = *hasher.compute();
22    hex::encode(hash)
23}
24
25type Md5 = [u8; 16];
26
27fn hash_genesis_constants(
28    constants: &MinaBaseProtocolConstantsCheckedValueStableV1,
29    tx_pool_max_size: &UnsignedExtendedUInt32StableV1,
30) -> [u8; 32] {
31    let mut hasher = Blake2b256::default();
32    let genesis_timestamp = OffsetDateTime::from_unix_timestamp_nanos(
33        (constants.genesis_state_timestamp.as_u64() * 1000000) as i128,
34    )
35    .unwrap();
36    let time_format =
37        format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:6]Z");
38    hasher.update(constants.k.to_string().as_bytes());
39    hasher.update(constants.slots_per_epoch.to_string().as_bytes());
40    hasher.update(constants.slots_per_sub_window.to_string().as_bytes());
41    hasher.update(constants.delta.to_string().as_bytes());
42    hasher.update(tx_pool_max_size.to_string().as_bytes());
43    hasher.update(genesis_timestamp.format(&time_format).unwrap().as_bytes());
44    hasher.finalize().try_into().unwrap()
45}
46
47impl ChainId {
48    pub fn compute(
49        constraint_system_digests: &[Md5],
50        genesis_state_hash: &StateHash,
51        genesis_constants: &MinaBaseProtocolConstantsCheckedValueStableV1,
52        protocol_transaction_version: u8,
53        protocol_network_version: u8,
54        tx_max_pool_size: &UnsignedExtendedUInt32StableV1,
55    ) -> ChainId {
56        let mut hasher = Blake2b256::default();
57        let constraint_system_hash = constraint_system_digests
58            .iter()
59            .map(hex::encode)
60            .reduce(|acc, el| acc + &el)
61            .unwrap_or_default();
62        let genesis_constants_hash = hash_genesis_constants(genesis_constants, tx_max_pool_size);
63        hasher.update(genesis_state_hash.to_string().as_bytes());
64        hasher.update(constraint_system_hash.to_string().as_bytes());
65        hasher.update(hex::encode(genesis_constants_hash).as_bytes());
66        hasher.update(md5_hash(protocol_transaction_version).as_bytes());
67        hasher.update(md5_hash(protocol_network_version).as_bytes());
68        ChainId(hasher.finalize().try_into().unwrap())
69    }
70
71    /// Computes shared key for libp2p Pnet protocol.
72    pub fn preshared_key(&self) -> [u8; 32] {
73        let mut hasher = Blake2b256::default();
74        hasher.update(b"/coda/0.0.1/");
75        hasher.update(self.to_hex().as_bytes());
76        let hash = hasher.finalize();
77        let mut psk_fixed: [u8; 32] = Default::default();
78        psk_fixed.copy_from_slice(hash.as_ref());
79        psk_fixed
80    }
81
82    pub fn to_hex(&self) -> String {
83        hex::encode(self.0)
84    }
85
86    pub fn from_hex(s: &str) -> Result<ChainId, hex::FromHexError> {
87        let h = hex::decode(s)?;
88        let bs = h[..32]
89            .try_into()
90            .or(Err(hex::FromHexError::InvalidStringLength))?;
91        Ok(ChainId(bs))
92    }
93
94    pub fn from_bytes(bytes: &[u8]) -> ChainId {
95        let mut arr = [0u8; 32];
96        arr.copy_from_slice(&bytes[..32]);
97        ChainId(arr)
98    }
99}
100
101impl BinProtWrite for ChainId {
102    fn binprot_write<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
103        w.write_all(&self.0)
104    }
105}
106
107impl BinProtRead for ChainId {
108    fn binprot_read<R: Read + ?Sized>(r: &mut R) -> Result<Self, binprot::Error>
109    where
110        Self: Sized,
111    {
112        let mut bytes = [0; 32];
113        r.read_exact(&mut bytes)?;
114        Ok(Self(bytes))
115    }
116}
117
118impl Serialize for ChainId {
119    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
120        serializer.serialize_str(&self.to_hex())
121    }
122}
123
124impl<'de> Deserialize<'de> for ChainId {
125    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
126        let s = String::deserialize(deserializer)?;
127        ChainId::from_hex(&s).map_err(serde::de::Error::custom)
128    }
129}
130
131impl AsRef<[u8]> for ChainId {
132    fn as_ref(&self) -> &[u8] {
133        &self.0
134    }
135}
136
137impl Display for ChainId {
138    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
139        write!(f, "{}", self.to_hex())?;
140        Ok(())
141    }
142}
143
144impl Debug for ChainId {
145    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
146        write!(f, "ChainId({})", self)
147    }
148}
149
150pub const DEVNET_CHAIN_ID: ChainId = ChainId([
151    0x29, 0x93, 0x61, 0x04, 0x44, 0x3a, 0xaf, 0x26, 0x4a, 0x7f, 0x01, 0x92, 0xac, 0x64, 0xb1, 0xc7,
152    0x17, 0x31, 0x98, 0xc1, 0xed, 0x40, 0x4c, 0x1b, 0xcf, 0xf5, 0xe5, 0x62, 0xe0, 0x5e, 0xb7, 0xf6,
153]);
154
155pub const MAINNET_CHAIN_ID: ChainId = ChainId([
156    0xa7, 0x35, 0x1a, 0xbc, 0x7d, 0xdf, 0x2e, 0xa9, 0x2d, 0x1b, 0x38, 0xcc, 0x8e, 0x63, 0x6c, 0x27,
157    0x1c, 0x1d, 0xfd, 0x2c, 0x08, 0x1c, 0x63, 0x7f, 0x62, 0xeb, 0xc2, 0xaf, 0x34, 0xeb, 0x7c, 0xc1,
158]);
159
160#[cfg(test)]
161mod test {
162    use time::format_description::well_known::Rfc3339;
163
164    use super::*;
165    use crate::constants::*;
166
167    #[test]
168    fn test_devnet_chain_id() {
169        // First block after fork: https://devnet.minaexplorer.com/block/3NL93SipJfAMNDBRfQ8Uo8LPovC74mnJZfZYB5SK7mTtkL72dsPx
170        let genesis_state_hash = "3NL93SipJfAMNDBRfQ8Uo8LPovC74mnJZfZYB5SK7mTtkL72dsPx"
171            .parse()
172            .unwrap();
173
174        let mut protocol_constants = PROTOCOL_CONSTANTS.clone();
175        protocol_constants.genesis_state_timestamp =
176            OffsetDateTime::parse("2024-04-09T21:00:00Z", &Rfc3339)
177                .unwrap()
178                .into();
179
180        // Compute the chain id for the Devnet network and compare it the real one.
181        let chain_id = ChainId::compute(
182            crate::network::devnet::CONSTRAINT_SYSTEM_DIGESTS.as_slice(),
183            &genesis_state_hash,
184            &protocol_constants,
185            PROTOCOL_TRANSACTION_VERSION,
186            PROTOCOL_NETWORK_VERSION,
187            &UnsignedExtendedUInt32StableV1::from(TX_POOL_MAX_SIZE),
188        );
189        assert_eq!(chain_id, DEVNET_CHAIN_ID);
190    }
191
192    #[test]
193    fn test_mainnet_chain_id() {
194        // First block after fork: https://www.minaexplorer.com/block/3NK4BpDSekaqsG6tx8Nse2zJchRft2JpnbvMiog55WCr5xJZaKeP
195        let genesis_state_hash = "3NK4BpDSekaqsG6tx8Nse2zJchRft2JpnbvMiog55WCr5xJZaKeP"
196            .parse()
197            .unwrap();
198
199        let mut protocol_constants = PROTOCOL_CONSTANTS.clone();
200        protocol_constants.genesis_state_timestamp =
201            OffsetDateTime::parse("2024-06-05T00:00:00Z", &Rfc3339)
202                .unwrap()
203                .into();
204
205        // Compute the chain id for the Mainnet network and compare it the real one.
206        let chain_id = ChainId::compute(
207            crate::network::mainnet::CONSTRAINT_SYSTEM_DIGESTS.as_slice(),
208            &genesis_state_hash,
209            &protocol_constants,
210            PROTOCOL_TRANSACTION_VERSION,
211            PROTOCOL_NETWORK_VERSION,
212            &UnsignedExtendedUInt32StableV1::from(TX_POOL_MAX_SIZE),
213        );
214        assert_eq!(chain_id, MAINNET_CHAIN_ID);
215    }
216
217    #[test]
218    fn test_devnet_chain_id_as_hex() {
219        assert_eq!(
220            DEVNET_CHAIN_ID.to_hex(),
221            "29936104443aaf264a7f0192ac64b1c7173198c1ed404c1bcff5e562e05eb7f6"
222        );
223    }
224
225    #[test]
226    fn test_mainnet_chain_id_as_hex() {
227        assert_eq!(
228            MAINNET_CHAIN_ID.to_hex(),
229            "a7351abc7ddf2ea92d1b38cc8e636c271c1dfd2c081c637f62ebc2af34eb7cc1"
230        );
231    }
232}