use std::fs;
use std::path::{Path, PathBuf};
use base64::prelude::{BASE64_STANDARD, Engine};
use namada_sdk::borsh::BorshSerializeExt;
use namada_sdk::key::*;
use rand_core::OsRng;
use serde_json::json;
use sha2::{Digest, Sha256};
use thiserror::Error;
use crate::tendermint::node::Id as TendermintNodeId;
use crate::tendermint_config::Error as TendermintError;
pub const ENV_VAR_TM_STDOUT: &str = "NAMADA_CMT_STDOUT";
#[derive(Error, Debug)]
pub enum Error {
#[error("Failed to initialize CometBFT: {0}")]
Init(std::io::Error),
#[error("Failed to load CometBFT config file: {0}")]
LoadConfig(TendermintError),
#[error("Failed to open CometBFT config for writing: {0}")]
OpenWriteConfig(std::io::Error),
#[error("Failed to serialize CometBFT config TOML to string: {0}")]
ConfigSerializeToml(toml::ser::Error),
#[error("Failed to write CometBFT config: {0}")]
WriteConfig(std::io::Error),
#[error("Failed to start up CometBFT node: {0}")]
StartUp(std::io::Error),
#[error("{0}")]
Runtime(String),
#[error("Failed to rollback CometBFT state: {0}")]
RollBack(String),
#[error("Failed to convert to String: {0:?}")]
TendermintPath(std::ffi::OsString),
#[error("Couldn't write {0}")]
CantWrite(String),
#[error("Couldn't create {0}")]
CantCreate(String),
#[error("Couldn't encode {0}")]
CantEncode(&'static str),
}
pub type Result<T> = std::result::Result<T, Error>;
pub fn validator_key_to_json(
sk: &common::SecretKey,
) -> std::result::Result<serde_json::Value, ParseSecretKeyError> {
let raw_hash = tm_consensus_key_raw_hash(&sk.ref_to());
let (id_str, pk_arr, kp_arr) = match sk {
common::SecretKey::Ed25519(_) => {
let sk_ed: ed25519::SecretKey = sk.try_to_sk().unwrap();
let keypair =
[sk_ed.serialize_to_vec(), sk_ed.ref_to().serialize_to_vec()]
.concat();
("Ed25519", sk_ed.ref_to().serialize_to_vec(), keypair)
}
common::SecretKey::Secp256k1(_) => {
let sk_sec: secp256k1::SecretKey = sk.try_to_sk().unwrap();
(
"Secp256k1",
sk_sec.ref_to().serialize_to_vec(),
sk_sec.serialize_to_vec(),
)
}
};
Ok(json!({
"address": raw_hash,
"pub_key": {
"type": format!("tendermint/PubKey{}",id_str),
"value": BASE64_STANDARD.encode(pk_arr),
},
"priv_key": {
"type": format!("tendermint/PrivKey{}",id_str),
"value": BASE64_STANDARD.encode(kp_arr),
}
}))
}
pub fn write_validator_key(
home_dir: impl AsRef<Path>,
consensus_key: &common::SecretKey,
) -> Result<()> {
let key = validator_key_to_json(consensus_key).unwrap();
write_validator(validator_key(home_dir), KEY_DIR, KEY_FILE, key)
}
pub fn write_validator_state(home_dir: impl AsRef<Path>) -> Result<()> {
let state = json!({
"height": "0",
"round": 0,
"step": 0
});
write_validator(validator_state(home_dir), STATE_DIR, STATE_FILE, state)
}
pub fn backup_validator_key_and_state(home_dir: impl AsRef<Path>) {
let key_path = validator_key(&home_dir);
let state_path = validator_state(&home_dir);
for path in [key_path, state_path] {
if path.exists() {
fs::rename(&path, path.with_extension("json.bk")).expect(
"Must be able to backup existing CometBFT validator key and \
state",
);
}
}
}
pub fn write_dummy_validator_key_and_state(home_dir: impl AsRef<Path>) {
let dummy_key: common::SecretKey = ed25519::SigScheme::generate(&mut OsRng)
.try_to_sk()
.unwrap();
write_validator_key(&home_dir, &dummy_key)
.expect("Must be able to write dummy validator key.");
write_validator_state(&home_dir)
.expect("Must be able to write dummy validator state.");
}
pub fn write_validator(
path: PathBuf,
err_dir: &'static str,
err_file: &'static str,
data: serde_json::Value,
) -> Result<()> {
let parent_dir = path.parent().unwrap();
fs::create_dir_all(parent_dir).map_err(|err| {
Error::CantCreate(format!(
"{} at {}. Caused by {err}",
err_dir,
parent_dir.to_string_lossy()
))
})?;
let file = ensure_empty(&path).map_err(|err| {
Error::CantCreate(format!(
"{} at {}. Caused by {err}",
err_dir,
path.to_string_lossy()
))
})?;
serde_json::to_writer_pretty(file, &data).map_err(|err| {
Error::CantWrite(format!(
"{} to {}. Caused by {err}",
err_file,
path.to_string_lossy()
))
})
}
const TENDERMINT_NODE_ID_LENGTH: usize = 20;
pub fn id_from_pk(pk: &common::PublicKey) -> TendermintNodeId {
let mut bytes = [0u8; TENDERMINT_NODE_ID_LENGTH];
match pk {
common::PublicKey::Ed25519(_) => {
let _pk: ed25519::PublicKey = pk.try_to_pk().unwrap();
let digest = Sha256::digest(_pk.serialize_to_vec().as_slice());
bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]);
}
common::PublicKey::Secp256k1(_) => {
let _pk: secp256k1::PublicKey = pk.try_to_pk().unwrap();
let digest = Sha256::digest(_pk.serialize_to_vec().as_slice());
bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]);
}
}
TendermintNodeId::new(bytes)
}
fn ensure_empty(path: &PathBuf) -> std::io::Result<fs::File> {
fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)
}
fn validator_key(home_dir: impl AsRef<Path>) -> PathBuf {
home_dir
.as_ref()
.join("config")
.join("priv_validator_key.json")
}
fn validator_state(home_dir: impl AsRef<Path>) -> PathBuf {
home_dir
.as_ref()
.join("data")
.join("priv_validator_state.json")
}
const KEY_FILE: &str = "private validator key file";
const KEY_DIR: &str = "private validator key directory";
const STATE_FILE: &str = "private validator state file";
const STATE_DIR: &str = "private validator state directory";