use anyhow::anyhow; use std::str::FromStr; use std::sync::Arc; use clap::Parser; use log::trace; use nym_task::TaskManager; use reqwest::Url; use crate::ephemera_api::ApplicationResult; use crate::utilities::codec::{Codec, EphemeraCodec}; use crate::{ api::application::CheckBlockResult, cli::PEERS_CONFIG_FILE, config::Configuration, crypto::EphemeraKeypair, crypto::Keypair, ephemera_api::{ApiBlock, ApiEphemeraMessage, Application, Dummy, RawApiEphemeraMessage}, network::members::ConfigMembersProvider, EphemeraStarterInit, }; #[derive(Clone, Debug)] pub struct HttpMembersProviderArg { pub url: Url, } impl FromStr for HttpMembersProviderArg { type Err = anyhow::Error; fn from_str(s: &str) -> Result { Ok(HttpMembersProviderArg { url: s.parse()? }) } } #[derive(Parser)] pub struct RunExternalNodeCmd { #[clap(short, long)] pub config_file: String, #[clap(short, long)] pub peers_config: String, } impl RunExternalNodeCmd { /// # Errors /// If the members provider cannot be created. /// /// # Panics /// If the ephemera cannot be created. pub async fn execute(&self) -> anyhow::Result<()> { let ephemera_conf = match Configuration::try_load(self.config_file.clone()) { Ok(conf) => conf, Err(err) => anyhow::bail!("Error loading configuration file: {err:?}"), }; let members_provider = Self::config_members_provider_with_path(self.peers_config.clone())?; let ephemera = EphemeraStarterInit::new(ephemera_conf.clone()) .unwrap() .with_application(Dummy) .with_members_provider(members_provider)? .build(); let shutdown = TaskManager::new(10); let shutdown_listener = shutdown.subscribe(); tokio::spawn(ephemera.run(shutdown_listener)); if let Err(err) = shutdown.catch_interrupt().await { Err(anyhow!("Shutdown error {:?}", err)) } else { Ok(()) } } #[allow(dead_code)] fn config_members_provider() -> anyhow::Result { let peers_conf_path = Configuration::ephemera_root_dir(None) .unwrap() .join(PEERS_CONFIG_FILE); let peers_conf = match ConfigMembersProvider::init(peers_conf_path) { Ok(conf) => conf, Err(err) => anyhow::bail!("Error loading peers file: {err:?}"), }; Ok(peers_conf) } #[allow(dead_code)] fn config_members_provider_with_path( peers_conf_path: String, ) -> anyhow::Result { let peers_conf = match ConfigMembersProvider::init(peers_conf_path) { Ok(conf) => conf, Err(err) => anyhow::bail!("Error loading peers file: {err:?}"), }; Ok(peers_conf) } } pub struct SignatureVerificationApplication { keypair: Arc, } impl SignatureVerificationApplication { #[must_use] pub fn new(keypair: Arc) -> Self { Self { keypair } } pub(crate) fn verify_message(&self, msg: ApiEphemeraMessage) -> anyhow::Result<()> { let signature = msg.certificate.clone(); let raw_message: RawApiEphemeraMessage = msg.into(); let encoded_message = Codec::encode(&raw_message)?; if self .keypair .verify(&encoded_message, &signature.signature.into()) { Ok(()) } else { anyhow::bail!("Invalid signature") } } } impl Application for SignatureVerificationApplication { fn check_tx(&self, tx: ApiEphemeraMessage) -> ApplicationResult { trace!("SignatureVerificationApplicationHook::check_tx"); self.verify_message(tx)?; Ok(true) } fn check_block(&self, _block: &ApiBlock) -> ApplicationResult { Ok(CheckBlockResult::Accept) } fn deliver_block(&self, _block: ApiBlock) -> ApplicationResult<()> { trace!("SignatureVerificationApplicationHook::deliver_block"); Ok(()) } }