1use std::{env, fmt};
6
7use fastcrypto::encoding::{Base58, Encoding, Hex};
8use iota_protocol_config::Chain;
9pub use iota_sdk_types::Digest;
10use once_cell::sync::{Lazy, OnceCell};
11use serde::{Deserialize, Serialize};
12use tracing::info;
13
14#[derive(
16 Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
17)]
18pub struct ChainIdentifier(pub(crate) CheckpointDigest);
19
20pub const MAINNET_CHAIN_IDENTIFIER_BASE58: &str = "7gzPnGnmjqvpmF7NXTCmtacXLqx1cMaJFV6GCmi1peqr";
21pub const TESTNET_CHAIN_IDENTIFIER_BASE58: &str = "3MhPzSaSTHGffwPSV2Ws2DaK8LR8DBGPozTd2CbiJRwe";
22
23pub static MAINNET_CHAIN_IDENTIFIER: OnceCell<ChainIdentifier> = OnceCell::new();
24pub static TESTNET_CHAIN_IDENTIFIER: OnceCell<ChainIdentifier> = OnceCell::new();
25
26const IOTA_PROTOCOL_CONFIG_CHAIN_OVERRIDE_ENV_VAR_NAME: &str =
30 "IOTA_PROTOCOL_CONFIG_CHAIN_OVERRIDE";
31
32static IOTA_PROTOCOL_CONFIG_CHAIN_OVERRIDE: Lazy<Option<Chain>> = Lazy::new(|| {
33 if let Ok(s) = env::var(IOTA_PROTOCOL_CONFIG_CHAIN_OVERRIDE_ENV_VAR_NAME) {
34 info!("IOTA_PROTOCOL_CONFIG_CHAIN_OVERRIDE: {:?}", s);
35 match s.as_str() {
36 "mainnet" => Some(Chain::Mainnet),
37 "testnet" => Some(Chain::Testnet),
38 "" => None,
39 _ => panic!("unrecognized IOTA_PROTOCOL_CONFIG_CHAIN_OVERRIDE: {s:?}"),
40 }
41 } else {
42 None
43 }
44});
45
46impl ChainIdentifier {
47 pub fn from_chain_short_id(short_id: impl AsRef<str>) -> Option<Self> {
51 if Hex::from_bytes(&Base58::decode(MAINNET_CHAIN_IDENTIFIER_BASE58).ok()?)
52 .encoded_with_format()
53 .starts_with(&format!("0x{}", short_id.as_ref()))
54 {
55 Some(get_mainnet_chain_identifier())
56 } else if Hex::from_bytes(&Base58::decode(TESTNET_CHAIN_IDENTIFIER_BASE58).ok()?)
57 .encoded_with_format()
58 .starts_with(&format!("0x{}", short_id.as_ref()))
59 {
60 Some(get_testnet_chain_identifier())
61 } else {
62 None
63 }
64 }
65
66 pub fn chain(&self) -> Chain {
67 let mainnet_id = get_mainnet_chain_identifier();
68 let testnet_id = get_testnet_chain_identifier();
69
70 let chain = match self {
71 id if *id == mainnet_id => Chain::Mainnet,
72 id if *id == testnet_id => Chain::Testnet,
73 _ => Chain::Unknown,
74 };
75 if let Some(override_chain) = *IOTA_PROTOCOL_CONFIG_CHAIN_OVERRIDE {
76 if chain != Chain::Unknown {
77 panic!("not allowed to override real chain {chain:?}");
78 }
79 return override_chain;
80 }
81
82 chain
83 }
84
85 pub fn as_bytes(&self) -> &[u8; 32] {
86 self.0.inner()
87 }
88
89 pub fn into_bytes(self) -> [u8; 32] {
90 self.0.into_inner()
91 }
92
93 pub fn digest(&self) -> CheckpointDigest {
94 self.0
95 }
96}
97
98pub fn get_mainnet_chain_identifier() -> ChainIdentifier {
99 let digest = MAINNET_CHAIN_IDENTIFIER.get_or_init(|| {
100 let digest = CheckpointDigest::new(
101 Base58::decode(MAINNET_CHAIN_IDENTIFIER_BASE58)
102 .expect("mainnet genesis checkpoint digest literal is invalid")
103 .try_into()
104 .expect("Mainnet genesis checkpoint digest literal has incorrect length"),
105 );
106 ChainIdentifier::from(digest)
107 });
108 *digest
109}
110
111pub fn get_testnet_chain_identifier() -> ChainIdentifier {
112 let digest = TESTNET_CHAIN_IDENTIFIER.get_or_init(|| {
113 let digest = CheckpointDigest::new(
114 Base58::decode(TESTNET_CHAIN_IDENTIFIER_BASE58)
115 .expect("testnet genesis checkpoint digest literal is invalid")
116 .try_into()
117 .expect("Testnet genesis checkpoint digest literal has incorrect length"),
118 );
119 ChainIdentifier::from(digest)
120 });
121 *digest
122}
123
124impl fmt::Display for ChainIdentifier {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 for byte in self.0.as_bytes()[0..4].iter() {
127 write!(f, "{byte:02x}")?;
128 }
129
130 Ok(())
131 }
132}
133
134impl From<CheckpointDigest> for ChainIdentifier {
135 fn from(digest: CheckpointDigest) -> Self {
136 Self(digest)
137 }
138}
139
140pub type CheckpointDigest = Digest;
141pub type CheckpointContentsDigest = Digest;
142pub type CertificateDigest = Digest;
143pub type SenderSignedDataDigest = Digest;
144pub type TransactionDigest = Digest;
145pub type TransactionEffectsDigest = Digest;
146pub type TransactionEventsDigest = Digest;
147pub type EffectsAuxDataDigest = Digest;
148pub type ObjectDigest = Digest;
149pub type ConsensusCommitDigest = Digest;
150pub type MoveAuthenticatorDigest = Digest;
151pub type MisbehaviorReportDigest = Digest;
152
153mod test {
154 #[allow(unused_imports)]
155 use crate::digests::ChainIdentifier;
156
157 #[test]
158 fn test_chain_id_mainnet() {
159 let chain_id = ChainIdentifier::from_chain_short_id("6364aad5");
160 assert_eq!(
161 chain_id.unwrap().chain(),
162 iota_protocol_config::Chain::Mainnet
163 );
164 }
165
166 #[test]
167 fn test_chain_id_testnet() {
168 let chain_id = ChainIdentifier::from_chain_short_id("2304aa97");
169 assert_eq!(
170 chain_id.unwrap().chain(),
171 iota_protocol_config::Chain::Testnet
172 );
173 }
174
175 #[test]
176 fn test_chain_id_unknown() {
177 let chain_id = ChainIdentifier::from_chain_short_id("unknown");
178 assert_eq!(chain_id, None);
179 }
180}