1use anyhow::Context;
2use decaf377_rdsa::{SigningKey, SpendAuth, VerificationKey};
3use directories::UserDirs;
4use penumbra_sdk_app::genesis::AppState;
5use penumbra_sdk_custody::soft_kms::Config as SoftKmsConfig;
6use penumbra_sdk_keys::keys::{SpendKey, SpendKeyBytes};
7use rand::Rng;
8use rand_core::OsRng;
9use regex::{Captures, Regex};
10use serde::Deserialize;
11use std::{
12 env::current_dir,
13 fs::{self, File},
14 io::Write,
15 net::SocketAddr,
16 path::PathBuf,
17 str::FromStr,
18};
19use tendermint::{node::Id, Genesis, Moniker, PrivateKey};
20use tendermint_config::{
21 net::Address as TendermintAddress, NodeKey, PrivValidatorKey, TendermintConfig,
22};
23use url::Url;
24
25use crate::network::generate::NetworkValidator;
26
27pub struct NetworkTendermintConfig(pub TendermintConfig);
29
30impl NetworkTendermintConfig {
31 pub fn new(
34 node_name: &str,
35 peers: Vec<TendermintAddress>,
36 external_address: Option<TendermintAddress>,
37 tm_rpc_bind: Option<SocketAddr>,
38 tm_p2p_bind: Option<SocketAddr>,
39 ) -> anyhow::Result<Self> {
40 tracing::debug!("List of CometBFT peers: {:?}", peers);
41 let moniker: Moniker = Moniker::from_str(node_name)?;
42 let mut tm_config = TendermintConfig::parse_toml(include_str!(
43 "../../../../../testnets/cometbft_config_template.toml"
44 ))
45 .context("Failed to parse the TOML config template for CometBFT")?;
46 tm_config.moniker = moniker;
47 tm_config.p2p.seeds = peers;
48 tracing::debug!("External address looks like: {:?}", external_address);
49 tm_config.p2p.external_address = external_address;
50 if let Some(rpc) = tm_rpc_bind {
52 tm_config.rpc.laddr =
53 parse_tm_address(None, &Url::parse(format!("tcp://{}", rpc).as_str())?)?;
54 }
55 if let Some(p2p) = tm_p2p_bind {
56 tm_config.p2p.laddr =
57 parse_tm_address(None, &Url::parse(format!("tcp://{}", p2p).as_str())?)?;
58 }
59
60 Ok(Self(tm_config))
61 }
62}
63
64impl NetworkTendermintConfig {
65 pub fn write_config(
68 &self,
69 node_dir: PathBuf,
70 v: &NetworkValidator,
71 genesis: &Genesis<AppState>,
72 ) -> anyhow::Result<()> {
73 let pd_dir = node_dir.clone().join("pd");
75 let cb_data_dir = node_dir.clone().join("cometbft").join("data");
76 let cb_config_dir = node_dir.clone().join("cometbft").join("config");
77
78 tracing::info!(config_dir = %node_dir.display(), "Writing validator configs to");
79
80 fs::create_dir_all(pd_dir)?;
81 fs::create_dir_all(&cb_data_dir)?;
82 fs::create_dir_all(&cb_config_dir)?;
83
84 let genesis_file_path = cb_config_dir.clone().join("genesis.json");
85 tracing::debug!(genesis_file_path = %genesis_file_path.display(), "writing genesis");
86 let mut genesis_file = File::create(genesis_file_path)?;
87 genesis_file.write_all(serde_json::to_string_pretty(&genesis)?.as_bytes())?;
88
89 let cb_config_filepath = cb_config_dir.clone().join("config.toml");
90 tracing::debug!(cometbft_config = %cb_config_filepath.display(), "writing cometbft config.toml");
91 let mut cb_config_file = File::create(cb_config_filepath)?;
92 cb_config_file.write_all(toml::to_string(&self.0)?.as_bytes())?;
93
94 let priv_key = tendermint::PrivateKey::Ed25519(
97 v.keys
98 .node_key_sk
99 .ed25519_signing_key()
100 .expect("node key has ed25519 signing key")
101 .clone(),
102 );
103
104 let node_key = NodeKey { priv_key };
105 let cb_node_key_filepath = cb_config_dir.clone().join("node_key.json");
106 tracing::debug!(cb_node_key_filepath = %cb_node_key_filepath.display(), "writing node key file");
107 let mut cb_node_key_file = File::create(cb_node_key_filepath)?;
108 cb_node_key_file.write_all(serde_json::to_string_pretty(&node_key)?.as_bytes())?;
109
110 let priv_validator_key_filepath = cb_config_dir.clone().join("priv_validator_key.json");
112 tracing::debug!(priv_validator_key_filepath = %priv_validator_key_filepath.display(), "writing validator private key");
113 let mut priv_validator_key_file = File::create(priv_validator_key_filepath)?;
114 let priv_validator_key: PrivValidatorKey = v.keys.priv_validator_key()?;
115 priv_validator_key_file
116 .write_all(serde_json::to_string_pretty(&priv_validator_key)?.as_bytes())?;
117
118 let priv_validator_state_filepath = cb_data_dir.clone().join("priv_validator_state.json");
120 tracing::debug!(priv_validator_state_filepath = %priv_validator_state_filepath.display(), "writing validator state");
121 let mut priv_validator_state_file = File::create(priv_validator_state_filepath)?;
122 priv_validator_state_file.write_all(NetworkValidator::initial_state().as_bytes())?;
123
124 let validator_spend_key_filepath = cb_config_dir.clone().join("validator_custody.json");
126 tracing::debug!(validator_spend_key_filepath = %validator_spend_key_filepath.display(), "writing validator custody file");
127 let mut validator_spend_key_file = File::create(validator_spend_key_filepath)?;
128 let validator_wallet = SoftKmsConfig::from(
129 SpendKey::try_from(v.keys.validator_spend_key.clone())
130 .expect("spend key should be valid"),
131 );
132 validator_spend_key_file
133 .write_all(toml::to_string_pretty(&validator_wallet)?.as_bytes())?;
134
135 Ok(())
136 }
137}
138
139pub fn parse_tm_address(
143 node_id: Option<&Id>,
144 node_address: &Url,
145) -> anyhow::Result<TendermintAddress> {
146 let hostname = match node_address.host() {
147 Some(h) => h,
148 None => {
149 anyhow::bail!(format!("Could not find hostname in URL: {}", node_address))
150 }
151 };
152 let port = node_address.port().unwrap_or(26656);
154 match node_id {
155 Some(id) => Ok(format!("{id}@{hostname}:{port}").parse()?),
156 None => Ok(format!("{hostname}:{port}").parse()?),
157 }
158}
159
160#[derive(Deserialize)]
163pub struct ValidatorKeys {
164 pub validator_id_sk: SigningKey<SpendAuth>,
166 pub validator_id_vk: VerificationKey<SpendAuth>,
167 pub validator_spend_key: SpendKeyBytes,
168 pub validator_cons_sk: tendermint::PrivateKey,
170 pub validator_cons_pk: tendermint::PublicKey,
171 pub node_key_sk: tendermint::PrivateKey,
173 #[allow(unused_variables, dead_code)]
174 pub node_key_pk: tendermint::PublicKey,
175}
176
177impl ValidatorKeys {
178 pub fn from_seed(seed: [u8; 32]) -> Self {
180 let seed = SpendKeyBytes(seed);
182 let spend_key = SpendKey::from(seed.clone());
183
184 let validator_id_sk = spend_key.spend_auth_key();
186 let validator_id_vk = VerificationKey::from(validator_id_sk);
187
188 let validator_cons_sk = ed25519_consensus::SigningKey::new(OsRng);
189
190 let validator_cons_sk = tendermint::PrivateKey::Ed25519(
192 validator_cons_sk
193 .as_bytes()
194 .as_slice()
195 .try_into()
196 .expect("32 bytes"),
197 );
198 let validator_cons_pk = validator_cons_sk.public_key();
199
200 let node_key_sk = ed25519_consensus::SigningKey::from(seed.0);
202 let signing_key_bytes = node_key_sk.as_bytes().as_slice();
203
204 let node_key_sk =
206 tendermint::PrivateKey::Ed25519(signing_key_bytes.try_into().expect("32 bytes"));
207 let node_key_pk = node_key_sk.public_key();
208
209 ValidatorKeys {
210 validator_id_sk: validator_id_sk.clone(),
211 validator_id_vk,
212 validator_cons_sk,
213 validator_cons_pk,
214 node_key_sk,
215 node_key_pk,
216 validator_spend_key: seed,
217 }
218 }
219
220 pub fn generate() -> Self {
221 let seed = SpendKeyBytes(OsRng.gen());
224 let spend_key = SpendKey::from(seed.clone());
225
226 let validator_id_sk = spend_key.spend_auth_key();
228 let validator_id_vk = VerificationKey::from(validator_id_sk);
229
230 let validator_cons_sk = ed25519_consensus::SigningKey::new(OsRng);
231
232 let validator_cons_sk = tendermint::PrivateKey::Ed25519(
234 validator_cons_sk
235 .as_bytes()
236 .as_slice()
237 .try_into()
238 .expect("32 bytes"),
239 );
240 let validator_cons_pk = validator_cons_sk.public_key();
241
242 let node_key_sk = ed25519_consensus::SigningKey::from(seed.0);
244 let signing_key_bytes = node_key_sk.as_bytes().as_slice();
245
246 let node_key_sk =
248 tendermint::PrivateKey::Ed25519(signing_key_bytes.try_into().expect("32 bytes"));
249 let node_key_pk = node_key_sk.public_key();
250
251 ValidatorKeys {
252 validator_id_sk: validator_id_sk.clone(),
253 validator_id_vk,
254 validator_cons_sk,
255 validator_cons_pk,
256 node_key_sk,
257 node_key_pk,
258 validator_spend_key: seed,
259 }
260 }
261 pub fn priv_validator_key(&self) -> anyhow::Result<PrivValidatorKey> {
264 let address: tendermint::account::Id = self.validator_cons_pk.into();
265 let priv_key = tendermint::PrivateKey::Ed25519(
266 self.validator_cons_sk
267 .ed25519_signing_key()
268 .ok_or_else(|| {
269 anyhow::anyhow!("Failed during loop of signing key for NetworkValidator")
270 })?
271 .clone(),
272 );
273 let priv_validator_key = PrivValidatorKey {
274 address,
275 pub_key: self.validator_cons_pk,
276 priv_key,
277 };
278 Ok(priv_validator_key)
279 }
280}
281
282impl Default for ValidatorKeys {
283 fn default() -> Self {
284 Self::generate()
285 }
286}
287
288#[derive(Deserialize)]
289pub struct TendermintNodeKey {
290 pub id: String,
291 pub priv_key: TendermintPrivKey,
292}
293
294#[derive(Deserialize)]
295pub struct TendermintPrivKey {
296 #[serde(rename(serialize = "type"))]
297 pub key_type: String,
298 pub value: PrivateKey,
299}
300
301pub fn canonicalize_path(input: &str) -> PathBuf {
304 let tilde = Regex::new(r"^~(/|$)").expect("tilde regex is valid");
305 if input.starts_with('/') {
306 input.into()
308 } else if tilde.is_match(input) {
309 PathBuf::from(&*tilde.replace(input, |c: &Captures| {
312 if let Some(user_dirs) = UserDirs::new() {
313 format!("{}{}", user_dirs.home_dir().to_string_lossy(), &c[1],)
314 } else {
315 c[0].to_string()
316 }
317 }))
318 } else {
319 PathBuf::from(format!(
320 "{}/{}",
321 current_dir()
322 .expect("current working dir is valid")
323 .display(),
324 input
325 ))
326 }
327}
328
329pub fn get_network_dir(network_dir: Option<PathBuf>) -> PathBuf {
332 match network_dir {
334 Some(o) => o,
335 None => canonicalize_path("~/.penumbra/network_data"),
336 }
337}
338
339pub fn url_has_necessary_parts(url: &Url) -> bool {
341 url.scheme() != "" && url.has_host() && url.port().is_some()
342}