iota_genesis_builder/
validator_info.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::bail;
6use fastcrypto::traits::ToFromBytes;
7use iota_types::{
8    base_types::IotaAddress,
9    crypto::{
10        AuthorityPublicKey, AuthorityPublicKeyBytes, AuthoritySignature, NetworkPublicKey,
11        verify_proof_of_possession,
12    },
13    multiaddr::Multiaddr,
14};
15use serde::{Deserialize, Serialize};
16use serde_with::serde_as;
17
18const MAX_VALIDATOR_METADATA_LENGTH: usize = 256;
19
20/// Publicly known information about a validator
21#[serde_as]
22#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
23#[serde(rename_all = "kebab-case")]
24pub struct ValidatorInfo {
25    pub name: String,
26    pub account_address: IotaAddress,
27    pub authority_key: AuthorityPublicKeyBytes,
28    pub protocol_key: NetworkPublicKey,
29    pub network_key: NetworkPublicKey,
30    pub gas_price: u64, // unused as of protocol version 5, but keeping for backwards compatibility
31    pub commission_rate: u64,
32    pub network_address: Multiaddr,
33    pub p2p_address: Multiaddr,
34    /// Primary address used for consensus-related inter-node communication.
35    pub primary_address: Multiaddr,
36    pub description: String,
37    pub image_url: String,
38    pub project_url: String,
39}
40
41impl ValidatorInfo {
42    pub fn name(&self) -> &str {
43        &self.name
44    }
45
46    pub fn iota_address(&self) -> IotaAddress {
47        self.account_address
48    }
49
50    pub fn authority_key(&self) -> AuthorityPublicKeyBytes {
51        self.authority_key
52    }
53
54    pub fn protocol_key(&self) -> &NetworkPublicKey {
55        &self.protocol_key
56    }
57
58    pub fn network_key(&self) -> &NetworkPublicKey {
59        &self.network_key
60    }
61
62    pub fn gas_price(&self) -> u64 {
63        self.gas_price
64    }
65
66    pub fn commission_rate(&self) -> u64 {
67        self.commission_rate
68    }
69
70    pub fn network_address(&self) -> &Multiaddr {
71        &self.network_address
72    }
73
74    pub fn primary_address(&self) -> &Multiaddr {
75        &self.primary_address
76    }
77
78    pub fn p2p_address(&self) -> &Multiaddr {
79        &self.p2p_address
80    }
81}
82
83#[serde_as]
84#[derive(Clone, Debug, Serialize, Deserialize)]
85pub struct GenesisValidatorInfo {
86    pub info: ValidatorInfo,
87    pub proof_of_possession: AuthoritySignature,
88}
89
90impl GenesisValidatorInfo {
91    pub fn validate(&self) -> anyhow::Result<(), anyhow::Error> {
92        if !self.info.name.is_ascii() {
93            bail!("name must be ascii");
94        }
95        if self.info.name.len() > MAX_VALIDATOR_METADATA_LENGTH {
96            bail!("name must be <= {MAX_VALIDATOR_METADATA_LENGTH} bytes long");
97        }
98
99        if !self.info.description.is_ascii() {
100            bail!("description must be ascii");
101        }
102        if self.info.description.len() > MAX_VALIDATOR_METADATA_LENGTH {
103            bail!("description must be <= {MAX_VALIDATOR_METADATA_LENGTH} bytes long");
104        }
105
106        if self.info.image_url.len() > MAX_VALIDATOR_METADATA_LENGTH {
107            bail!("image url must be <= {MAX_VALIDATOR_METADATA_LENGTH} bytes long");
108        }
109
110        if self.info.project_url.len() > MAX_VALIDATOR_METADATA_LENGTH {
111            bail!("project url must be <= {MAX_VALIDATOR_METADATA_LENGTH} bytes long");
112        }
113
114        if !self.info.network_address.to_string().is_ascii() {
115            bail!("network address must be ascii");
116        }
117        if self.info.network_address.len() > MAX_VALIDATOR_METADATA_LENGTH {
118            bail!("network address must be <= {MAX_VALIDATOR_METADATA_LENGTH} bytes long");
119        }
120
121        if !self.info.p2p_address.to_string().is_ascii() {
122            bail!("p2p address must be ascii");
123        }
124        if self.info.p2p_address.len() > MAX_VALIDATOR_METADATA_LENGTH {
125            bail!("p2p address must be <= {MAX_VALIDATOR_METADATA_LENGTH} bytes long");
126        }
127
128        if !self.info.primary_address.to_string().is_ascii() {
129            bail!("primary address must be ascii");
130        }
131        if self.info.primary_address.len() > MAX_VALIDATOR_METADATA_LENGTH {
132            bail!("primary address must be <= {MAX_VALIDATOR_METADATA_LENGTH} bytes long");
133        }
134
135        if let Err(e) = self.info.p2p_address.to_anemo_address() {
136            bail!("p2p address must be valid anemo address: {e}");
137        }
138        if let Err(e) = self.info.primary_address.to_anemo_address() {
139            bail!("primary address must be valid anemo address: {e}");
140        }
141
142        if self.info.commission_rate > 10000 {
143            bail!("commissions rate must be lower than 100%");
144        }
145
146        let authority_pubkey = AuthorityPublicKey::from_bytes(self.info.authority_key.as_ref())?;
147        if let Err(e) = verify_proof_of_possession(
148            &self.proof_of_possession,
149            &authority_pubkey,
150            self.info.account_address,
151        ) {
152            bail!("proof of possession is incorrect: {e}");
153        }
154
155        if self.info.protocol_key() == self.info.network_key() {
156            bail!("protocol pubkey must be different from the network pubkey");
157        }
158
159        Ok(())
160    }
161}
162
163impl From<GenesisValidatorInfo> for GenesisValidatorMetadata {
164    fn from(
165        GenesisValidatorInfo {
166            info,
167            proof_of_possession,
168        }: GenesisValidatorInfo,
169    ) -> Self {
170        Self {
171            name: info.name,
172            description: info.description,
173            image_url: info.image_url,
174            project_url: info.project_url,
175            iota_address: info.account_address,
176            gas_price: info.gas_price,
177            commission_rate: info.commission_rate,
178            authority_public_key: info.authority_key.as_bytes().to_vec(),
179            proof_of_possession: proof_of_possession.as_ref().to_vec(),
180            network_public_key: info.network_key.as_bytes().to_vec(),
181            protocol_public_key: info.protocol_key.as_bytes().to_vec(),
182            network_address: info.network_address,
183            p2p_address: info.p2p_address,
184            primary_address: info.primary_address,
185        }
186    }
187}
188
189#[derive(Clone, Debug, Serialize, Deserialize)]
190#[serde(rename_all = "kebab-case")]
191pub struct GenesisValidatorMetadata {
192    pub name: String,
193    pub description: String,
194    pub image_url: String,
195    pub project_url: String,
196
197    pub iota_address: IotaAddress,
198
199    pub gas_price: u64,
200    pub commission_rate: u64,
201
202    pub authority_public_key: Vec<u8>, // AuthorityPublicKeyBytes,
203    pub proof_of_possession: Vec<u8>,  // AuthoritySignature,
204
205    pub network_public_key: Vec<u8>,  // NetworkPublicKey,
206    pub protocol_public_key: Vec<u8>, // NetworkPublicKey,
207
208    pub network_address: Multiaddr,
209    pub p2p_address: Multiaddr,
210    pub primary_address: Multiaddr,
211}