iota_genesis_builder/
validator_info.rs1use 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#[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, pub commission_rate: u64,
32 pub network_address: Multiaddr,
33 pub p2p_address: Multiaddr,
34 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>, pub proof_of_possession: Vec<u8>, pub network_public_key: Vec<u8>, pub protocol_public_key: Vec<u8>, pub network_address: Multiaddr,
209 pub p2p_address: Multiaddr,
210 pub primary_address: Multiaddr,
211}