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 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 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 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 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 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}