iota_types/
digests.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use 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/// Representation of a network's identifier by the genesis checkpoint's digest
15#[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
26/// For testing purposes or bootstrapping regenesis chain configuration, you can
27/// set this environment variable to force protocol config to use a specific
28/// Chain.
29const 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    /// Take a short 4 byte identifier and convert it into a ChainIdentifier.
48    /// Short ids come from the JSON RPC getChainIdentifier and are encoded in
49    /// hex.
50    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}