mod blockchain; mod cli; mod client; mod config; mod error; mod logger; mod notify; mod response; use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicBool, Ordering}; use lazy_static::lazy_static; use crate::blockchain::BlockchainIter; use crate::cli::parse_cli; use crate::client::RpcClient; use crate::config::{Config, ContractVersion}; use crate::error::{Error, Result}; use crate::logger::Logger; use crate::notify::{Notification, Notifier}; lazy_static! { // Tracks whether or not the environment variables have been loaded from the .env file. static ref LOADED_ENV_FILE: AtomicBool = AtomicBool::new(false); } /// Attempts to load the .env file once at the start of the main process or at the start of the /// tests. Panics if the .env file cannot be found or if it cannot be parsed (most likely it /// contains invalid UTF-8 bytes). fn load_env_file() { if !LOADED_ENV_FILE.load(Ordering::Relaxed) { match dotenv::dotenv() { Ok(_) => LOADED_ENV_FILE.store(true, Ordering::Relaxed), Err(dotenv::Error::Io(_)) => panic!("could not find .env file"), _ => panic!("could not parse .env file"), }; } } /// Sets up ctrl-c to change the value of `poagov_is_running` from `true` to `false`. When /// `poagov_is_running` changes to `false`, the `poagov` process begins to gracefully shut down. /// The `AtomicBool` returned by this function is used to indicate whether or not the `poagov` /// binary should continue running. fn set_ctrlc_handler(logger: Arc>) -> Result> { let poagov_is_running = Arc::new(AtomicBool::new(true)); let setup_res = { let poagov_is_running = poagov_is_running.clone(); ctrlc::set_handler(move || { logger.lock().unwrap().log_ctrlc_pressed(); poagov_is_running.store(false, Ordering::SeqCst); }) }; if let Err(e) = setup_res { Err(Error::CtrlcSetupError(e)) } else { Ok(poagov_is_running) } } fn main() -> Result<()> { load_env_file(); let cli = parse_cli(); let config = Config::new(&cli)?; let logger = Arc::new(Mutex::new(Logger::new(&config))); let running = set_ctrlc_handler(logger.clone())?; let client = RpcClient::new(config.endpoint.clone()); let blockchain_iter = BlockchainIter::new(&client, &config, running)?; let mut notifier = Notifier::new(&config, logger.clone())?; // If email notifications have been enabled but there are no email recipients configured, warn // the user. if config.email_notifications && config.email_recipients.is_empty() { logger.lock().unwrap().log_no_email_recipients_configured(); } logger.lock().unwrap().log_starting_poagov(); 'blockchain_walker: for block_range_res in blockchain_iter { let (start_block, stop_block) = block_range_res?; let mut notifications = vec![]; // For each contract that we are monitoring for governance events, get the ballot-created // events that fall within the current `BlockchainIter`'s block window, convert those // ballot-created logs to `Notification`s. for contract in config.contracts.iter() { let ballot_created_logs = client.get_ballot_created_logs( contract, start_block, stop_block, )?; for log in ballot_created_logs { let notification = match contract.version { ContractVersion::V1 => { let voting_state = client.get_voting_state(contract, log.ballot_id)?; Notification::from_voting_state(&config, log, voting_state) } ContractVersion::V2 => { let ballot_info = client.get_ballot_info(contract, log.ballot_id)?; Notification::from_ballot_info(&config, log, ballot_info) } }; notifications.push(notification); } } // Sort the notifications by ascending block number. notifications.sort_unstable_by(|notif1, notif2| { notif1.log().block_number.cmp(¬if2.log().block_number) }); // Notify the governance notifications recipients. for notification in notifications { notifier.notify(¬ification); if notifier.reached_limit() { let limit = config.notification_limit.unwrap(); logger.lock().unwrap().log_reached_notification_limit(limit); break 'blockchain_walker; } } logger .lock() .unwrap() .log_finished_block_window(start_block, stop_block); } Ok(()) } #[cfg(test)] pub mod tests { use super::load_env_file; use crate::config::{ContractType, ContractVersion, Network}; pub const CORE_NETWORK: Network = Network::Core; pub const SOKOL_NETWORK: Network = Network::Sokol; pub const V1_VERSION: ContractVersion = ContractVersion::V1; pub const V2_VERSION: ContractVersion = ContractVersion::V2; pub const V1_CONTRACT_TYPES: [ContractType; 3] = [ ContractType::Keys, ContractType::Threshold, ContractType::Proxy, ]; pub const V2_CONTRACT_TYPES: [ContractType; 4] = [ ContractType::Keys, ContractType::Threshold, ContractType::Proxy, ContractType::Emission, ]; /// Loads the .env file once at the start of the tests. pub fn setup() { load_env_file(); } }