Applied a handful of cherry-picked rustfmt and clippy changes.

This commit is contained in:
DrPeterVanNostrand 2018-12-14 19:58:33 +00:00
parent 0150df2f8e
commit a29b43c26c
10 changed files with 264 additions and 184 deletions

View File

@ -16,8 +16,9 @@ fn create_env_file_if_dne() {
if !sample_env_path.exists() { if !sample_env_path.exists() {
panic!("neither the `.env` nor the `sample.env` files exist, one of these files must exist to build the `poagov`"); panic!("neither the `.env` nor the `sample.env` files exist, one of these files must exist to build the `poagov`");
} }
fs::copy(sample_env_path, env_path) if let Err(e) = fs::copy(sample_env_path, env_path) {
.unwrap_or_else(|e| panic!("failed to create the `.env` file from `sample.env`: {:?}", e)); panic!("failed to create the `.env` file from `sample.env`: {:?}", e);
}
} }
} }

View File

@ -9,7 +9,21 @@ use crate::client::RpcClient;
use crate::config::{Config, StartBlock}; use crate::config::{Config, StartBlock};
use crate::error::{Error, Result}; use crate::error::{Error, Result};
fn sleep_or_ctrlc(n_secs: u64, running: Arc<AtomicBool>) -> Option<()> { /// Represents the reason why the sleep cycle in `fn sleep_or_ctrlc()` ended.
#[derive(PartialEq)]
enum SleepExit {
CtrlC,
FinishedSleeping,
}
/// Sleeps for `n_secs` number of seconds or returns early if the user shuts down `poagov` using
/// ctrl-c.
///
/// Returns `SleepExit::CtrlC` if the user hit ctrl-c while this function was was sleeping or
/// returns `SleepExit::FinishedSleeping` if the function was able to sleep for the entire `n_secs`
/// duration.
fn sleep_or_ctrlc(n_secs: u64, running: Arc<AtomicBool>) -> SleepExit {
// This `AtomicBool` will become `true` when we have slept for `n_secs`.
let done_sleeping = Arc::new(AtomicBool::new(false)); let done_sleeping = Arc::new(AtomicBool::new(false));
{ {
let done_sleeping = done_sleeping.clone(); let done_sleeping = done_sleeping.clone();
@ -18,16 +32,16 @@ fn sleep_or_ctrlc(n_secs: u64, running: Arc<AtomicBool>) -> Option<()> {
done_sleeping.store(true, Ordering::SeqCst); done_sleeping.store(true, Ordering::SeqCst);
}); });
} }
loop { while !done_sleeping.load(Ordering::SeqCst) {
if !running.load(Ordering::SeqCst) { if !running.load(Ordering::SeqCst) {
return None; return SleepExit::CtrlC;
}
if done_sleeping.load(Ordering::SeqCst) {
return Some(());
} }
} }
SleepExit::FinishedSleeping
} }
/// A type that we use to iterate over the blocks in a blockchain in discrete block-windows (each
/// "block-window" is an inclusively bounded range of block numbers).
pub struct BlockchainIter<'a> { pub struct BlockchainIter<'a> {
client: &'a RpcClient, client: &'a RpcClient,
start_block: u64, start_block: u64,
@ -38,6 +52,16 @@ pub struct BlockchainIter<'a> {
} }
impl<'a> BlockchainIter<'a> { impl<'a> BlockchainIter<'a> {
/// Creates a new `BlockchainIter`.
///
/// # Errors
///
/// Return an error if the HTTP-RPC server cannot be reached or if the response from the RPC
/// server cannot be parsed.
///
/// Returns an `Error::StartBlockExceedsLastBlockMined` if the `start_block` that the user
/// passed in via a CLI argument is in the future (i.e. is greater than the block number of the
/// most recently mined block).
pub fn new(client: &'a RpcClient, config: &Config, running: Arc<AtomicBool>) -> Result<Self> { pub fn new(client: &'a RpcClient, config: &Config, running: Arc<AtomicBool>) -> Result<Self> {
let last_mined_block = client.get_last_mined_block_number()?; let last_mined_block = client.get_last_mined_block_number()?;
let start_block = match config.start_block { let start_block = match config.start_block {
@ -47,17 +71,19 @@ impl<'a> BlockchainIter<'a> {
StartBlock::Tail(tail) => last_mined_block - tail, StartBlock::Tail(tail) => last_mined_block - tail,
}; };
if start_block > last_mined_block { if start_block > last_mined_block {
return Err(Error::StartBlockExceedsLastBlockMined { start_block, last_mined_block }); return Err(Error::StartBlockExceedsLastBlockMined {
start_block,
last_mined_block,
});
} }
let bc_iter = BlockchainIter { Ok(BlockchainIter {
client, client,
start_block, start_block,
stop_block: last_mined_block, stop_block: last_mined_block,
on_first_iteration: true, on_first_iteration: true,
block_time: config.block_time, block_time: config.block_time,
running, running,
}; })
Ok(bc_iter)
} }
} }
@ -70,9 +96,11 @@ impl<'a> Iterator for BlockchainIter<'a> {
} else { } else {
self.start_block = self.stop_block + 1; self.start_block = self.stop_block + 1;
while self.start_block >= self.stop_block { while self.start_block >= self.stop_block {
sleep_or_ctrlc(self.block_time, self.running.clone())?; if sleep_or_ctrlc(self.block_time, self.running.clone()) == SleepExit::CtrlC {
match self.client.get_last_mined_block_number() { return None;
Ok(last_mined) => self.stop_block = last_mined, }
self.stop_block = match self.client.get_last_mined_block_number() {
Ok(last_mined) => last_mined,
Err(e) => return Some(Err(e)), Err(e) => return Some(Err(e)),
}; };
} }

View File

@ -1,7 +1,7 @@
// Some of `Cli`'s methods are not currently being used. // Some of `Cli`'s methods are not currently being used.
#![allow(dead_code)] #![allow(dead_code)]
use clap::{ArgMatches, App}; use clap::{App, ArgMatches};
pub fn parse_cli() -> Cli { pub fn parse_cli() -> Cli {
let cli_args = App::new("poagov") let cli_args = App::new("poagov")
@ -58,6 +58,10 @@ impl Cli {
self.0.is_present("emission") self.0.is_present("emission")
} }
pub fn no_contracts_specified(&self) -> bool {
!self.keys() && !self.threshold() && !self.proxy() && !self.emission()
}
pub fn v1(&self) -> bool { pub fn v1(&self) -> bool {
self.0.is_present("v1") self.0.is_present("v1")
} }

View File

@ -6,8 +6,9 @@ use web3::types::{Address, BlockNumber, Filter, FilterBuilder, U256};
use crate::config::{ContractType, PoaContract}; use crate::config::{ContractType, PoaContract};
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::response::{v1, v2};
use crate::response::common::BallotCreatedLog; use crate::response::common::BallotCreatedLog;
use crate::response::v1;
use crate::response::v2;
#[derive(Debug)] #[derive(Debug)]
pub enum RpcMethod { pub enum RpcMethod {
@ -43,8 +44,7 @@ impl RpcClient {
&self, &self,
method: RpcMethod, method: RpcMethod,
params: Vec<json::Value>, params: Vec<json::Value>,
) -> Result<reqwest::Request> ) -> Result<reqwest::Request> {
{
let method_call = json_rpc::types::request::MethodCall { let method_call = json_rpc::types::request::MethodCall {
jsonrpc: Some(json_rpc::types::version::Version::V2), jsonrpc: Some(json_rpc::types::version::Version::V2),
method: method.into(), method: method.into(),
@ -60,7 +60,8 @@ impl RpcClient {
} }
fn send(&self, req: reqwest::Request) -> Result<json::Value> { fn send(&self, req: reqwest::Request) -> Result<json::Value> {
let resp: json_rpc::types::response::Response = self.client let resp: json_rpc::types::response::Response = self
.client
.execute(req) .execute(req)
.map_err(|e| Error::RequestFailed(e))? .map_err(|e| Error::RequestFailed(e))?
.json() .json()
@ -68,7 +69,9 @@ impl RpcClient {
if let json_rpc::types::response::Response::Single(resp_status) = resp { if let json_rpc::types::response::Response::Single(resp_status) = resp {
match resp_status { match resp_status {
json_rpc::types::response::Output::Success(resp) => return Ok(resp.result), json_rpc::types::response::Output::Success(resp) => return Ok(resp.result),
json_rpc::types::response::Output::Failure(e) => return Err(Error::JsonRpcResponseFailure(e)), json_rpc::types::response::Output::Failure(e) => {
return Err(Error::JsonRpcResponseFailure(e))
}
}; };
} }
unreachable!("Recieved multiple responses for single request"); unreachable!("Recieved multiple responses for single request");
@ -97,8 +100,7 @@ impl RpcClient {
contract: &PoaContract, contract: &PoaContract,
start: BlockNumber, start: BlockNumber,
stop: BlockNumber, stop: BlockNumber,
) -> Result<Vec<BallotCreatedLog>> ) -> Result<Vec<BallotCreatedLog>> {
{
let event = contract.event("BallotCreated"); let event = contract.event("BallotCreated");
let event_sig = event.signature(); let event_sig = event.signature();
let filter = FilterBuilder::default() let filter = FilterBuilder::default()
@ -110,9 +112,15 @@ impl RpcClient {
self.get_logs(filter)? self.get_logs(filter)?
.into_iter() .into_iter()
.map(|web3_log| { .map(|web3_log| {
let web3::types::Log {topics, data, block_number, .. } = web3_log; let web3::types::Log {
topics,
data,
block_number,
..
} = web3_log;
let raw_log = ethabi::RawLog::from((topics, data.0)); let raw_log = ethabi::RawLog::from((topics, data.0));
let ethabi_log = event.parse_log(raw_log) let ethabi_log = event
.parse_log(raw_log)
.map_err(|e| Error::FailedToParseRawLogToLog(e))?; .map_err(|e| Error::FailedToParseRawLogToLog(e))?;
BallotCreatedLog::from_ethabi_log(ethabi_log, block_number.unwrap()) BallotCreatedLog::from_ethabi_log(ethabi_log, block_number.unwrap())
}) })
@ -120,7 +128,11 @@ impl RpcClient {
} }
/// V1 /// V1
pub fn get_voting_state(&self, contract: &PoaContract, ballot_id: U256) -> Result<v1::VotingState> { pub fn get_voting_state(
&self,
contract: &PoaContract,
ballot_id: U256,
) -> Result<v1::VotingState> {
let function = contract.function("votingState"); let function = contract.function("votingState");
let tokens = vec![ethabi::Token::Uint(ballot_id)]; let tokens = vec![ethabi::Token::Uint(ballot_id)];
let encoded_input = function.encode_input(&tokens).unwrap(); let encoded_input = function.encode_input(&tokens).unwrap();
@ -156,8 +168,12 @@ impl RpcClient {
// TODO: When V2 contracts have been published and ballots have begun, test that calling // TODO: When V2 contracts have been published and ballots have begun, test that calling
// `.getBallotInfo()` with `Address::zero()` for the `votingKey` works (we don't care if // `.getBallotInfo()` with `Address::zero()` for the `votingKey` works (we don't care if
// `votingKey` has voted yet). // `votingKey` has voted yet).
pub fn get_ballot_info(&self, contract: &PoaContract, ballot_id: U256) -> Result<v2::BallotInfo> { pub fn get_ballot_info(
// pub fn get_ballot_info(&self, contract: &PoaContract, ballot_id: U256, voting_key: Option<Address>) -> Result<v2::BallotInfo> { &self,
contract: &PoaContract,
ballot_id: U256,
) -> Result<v2::BallotInfo> {
// pub fn get_ballot_info(&self, contract: &PoaContract, ballot_id: U256, voting_key: Option<Address>) -> Result<v2::BallotInfo> {
let function = contract.function("getBallotInfo"); let function = contract.function("getBallotInfo");
/* /*
let mut tokens = vec![ethabi::Token::Uint(ballot_id)]; let mut tokens = vec![ethabi::Token::Uint(ballot_id)];
@ -208,15 +224,16 @@ mod tests {
use web3::types::BlockNumber; use web3::types::BlockNumber;
use crate::tests::setup;
use crate::config::{ContractType, ContractVersion, Network, PoaContract};
use super::RpcClient; use super::RpcClient;
use crate::config::{ContractType, ContractVersion, Network, PoaContract};
use crate::tests::setup;
#[test] #[test]
fn test_get_last_mined_block() { fn test_get_last_mined_block() {
setup(); setup();
let sokol_url = env::var("SOKOL_RPC_ENDPOINT").expect("Missing env-var: `SOKOL_RPC_ENDPOINT`"); let sokol_url =
env::var("SOKOL_RPC_ENDPOINT").expect("Missing env-var: `SOKOL_RPC_ENDPOINT`");
let client = RpcClient::new(sokol_url); let client = RpcClient::new(sokol_url);
let res = client.get_last_mined_block_number(); let res = client.get_last_mined_block_number();
println!("\nsokol last mined block number => {:?}", res); println!("\nsokol last mined block number => {:?}", res);
@ -232,18 +249,13 @@ mod tests {
#[test] #[test]
fn test_get_ballot_created_logs() { fn test_get_ballot_created_logs() {
setup(); setup();
let contract = PoaContract::read( let contract = PoaContract::read(ContractType::Keys, Network::Sokol, ContractVersion::V1)
ContractType::Keys, .unwrap_or_else(|e| panic!("Failed to load contract: {:?}", e));
Network::Sokol, let endpoint =
ContractVersion::V1, env::var("SOKOL_RPC_ENDPOINT").expect("Missing env-var: `SOKOL_RPC_ENDPOINT`");
).unwrap_or_else(|e| panic!("Failed to load contract: {:?}", e));
let endpoint = env::var("SOKOL_RPC_ENDPOINT").expect("Missing env-var: `SOKOL_RPC_ENDPOINT`");
let client = RpcClient::new(endpoint); let client = RpcClient::new(endpoint);
let res = client.get_ballot_created_logs( let res =
&contract, client.get_ballot_created_logs(&contract, BlockNumber::Earliest, BlockNumber::Latest);
BlockNumber::Earliest,
BlockNumber::Latest,
);
println!("{:#?}", res); println!("{:#?}", res);
assert!(res.is_ok()); assert!(res.is_ok());
} }
@ -273,12 +285,10 @@ mod tests {
#[test] #[test]
fn test_get_voting_state() { fn test_get_voting_state() {
setup(); setup();
let contract = PoaContract::read( let contract = PoaContract::read(ContractType::Threshold, Network::Sokol, ContractVersion::V1)
ContractType::Threshold, .unwrap_or_else(|e| panic!("Failed to load contract: {:?}", e));
Network::Sokol, let sokol_url = env::var("SOKOL_RPC_ENDPOINT")
ContractVersion::V1 .expect("Missing env-var: `SOKOL_RPC_ENDPOINT`");
).unwrap_or_else(|e| panic!("Failed to load contract: {:?}", e));
let sokol_url = env::var("SOKOL_RPC_ENDPOINT").expect("Missing env-var: `SOKOL_RPC_ENDPOINT`");
let client = RpcClient::new(sokol_url); let client = RpcClient::new(sokol_url);
let res = client.get_voting_state(&contract, 2.into()); let res = client.get_voting_state(&contract, 2.into());
println!("{:#?}", res); println!("{:#?}", res);

View File

@ -50,6 +50,10 @@ impl From<BallotType> for ContractType {
} }
impl ContractType { impl ContractType {
fn is_emission(&self) -> bool {
*self == ContractType::Emission
}
fn uppercase(&self) -> &str { fn uppercase(&self) -> &str {
match self { match self {
ContractType::Keys => "KEYS", ContractType::Keys => "KEYS",
@ -76,6 +80,10 @@ pub enum ContractVersion {
} }
impl ContractVersion { impl ContractVersion {
fn is_v1(&self) -> bool {
*self == ContractVersion::V1
}
fn lowercase(&self) -> &str { fn lowercase(&self) -> &str {
match self { match self {
ContractVersion::V1 => "v1", ContractVersion::V1 => "v1",
@ -103,46 +111,44 @@ impl Debug for PoaContract {
} }
impl PoaContract { impl PoaContract {
fn new(
kind: ContractType,
version: ContractVersion,
addr: Address,
abi: ethabi::Contract,
) -> Self {
PoaContract { kind, version, addr, abi }
}
pub fn read( pub fn read(
contract_type: ContractType, contract_type: ContractType,
network: Network, network: Network,
version: ContractVersion, version: ContractVersion,
) -> Result<Self> ) -> Result<Self> {
{ // Exit quickly if we know that the contract does not exist.
if contract_type == ContractType::Emission && version == ContractVersion::V1 { if contract_type.is_emission() && version.is_v1() {
return Err(Error::EmissionFundsV1ContractDoesNotExist); return Err(Error::EmissionFundsV1ContractDoesNotExist);
} }
let addr_env_var = format!( let env_var = format!(
"{}_CONTRACT_ADDRESS_{}_{:?}", "{}_CONTRACT_ADDRESS_{}_{:?}",
contract_type.uppercase(), contract_type.uppercase(),
network.uppercase(), network.uppercase(),
version, version
); );
let contract_addr_str = env::var(&env_var).map_err(|_| Error::MissingEnvVar(env_var))?;
let addr = if let Ok(s) = env::var(&addr_env_var) { let contract_addr = Address::from_str(contract_addr_str.trim_left_matches("0x"))
match Address::from_str(s.trim_left_matches("0x")) { .map_err(|_| Error::InvalidContractAddr(contract_addr_str.to_string()))?;
Ok(addr) => addr,
Err(_) => return Err(Error::InvalidContractAddr(s.into())),
}
} else {
return Err(Error::MissingEnvVar(addr_env_var));
};
let abi_path = format!( let abi_path = format!(
"abis/{}/{}", "abis/{}/{}",
version.lowercase(), version.lowercase(),
contract_type.abi_file_name(), contract_type.abi_file_name()
); );
let abi_file = File::open(&abi_path).map_err(|_| Error::MissingAbiFile(abi_path.clone()))?;
let abi = Contract::load(&abi_file).map_err(|_| Error::InvalidAbi(abi_path))?;
let abi_file = File::open(&abi_path) Ok(PoaContract::new(contract_type, version, contract_addr, abi))
.map_err(|_| Error::MissingAbiFile(abi_path.clone()))?;
let abi = Contract::load(&abi_file)
.map_err(|_| Error::InvalidAbi(abi_path))?;
let contract = PoaContract { kind: contract_type, version, addr, abi };
Ok(contract)
} }
pub fn event(&self, event: &str) -> Event { pub fn event(&self, event: &str) -> Event {
@ -184,85 +190,78 @@ pub struct Config {
impl Config { impl Config {
pub fn new(cli: &Cli) -> Result<Self> { pub fn new(cli: &Cli) -> Result<Self> {
let network = if cli.core() == cli.sokol() { if cli.core() == cli.sokol() {
return Err(Error::MustSpecifyOneCliArgument("`--core` or `--sokol`".into())); return Err(Error::MustSpecifyOneCliArgument("--core, --sokol".to_string()));
} else if cli.core() { }
if cli.v1() == cli.v2() {
return Err(Error::MustSpecifyOneCliArgument("--v1, --v2".to_string()));
}
if cli.no_contracts_specified() {
return Err(Error::MustSpecifyAtLeastOneCliArgument(
"--keys, --threshold, --proxy, --emission".to_string().to_string(),
));
}
if cli.multiple_start_blocks_specified() {
return Err(Error::MustSpecifyOneCliArgument(
"--earliest, --latest, --start-block, --tail".to_string()
));
}
let network = if cli.core() {
Network::Core Network::Core
} else { } else {
Network::Sokol Network::Sokol
}; };
let version = if cli.v1() {
ContractVersion::V1
} else {
ContractVersion::V2
};
let endpoint_env_var = format!("{}_RPC_ENDPOINT", network.uppercase()); let endpoint_env_var = format!("{}_RPC_ENDPOINT", network.uppercase());
let endpoint = env::var(&endpoint_env_var) let endpoint = env::var(&endpoint_env_var)
.map_err(|_| Error::MissingEnvVar(endpoint_env_var))?; .map_err(|_| Error::MissingEnvVar(endpoint_env_var))?;
let version = if cli.v1() == cli.v2(){
return Err(Error::MustSpecifyOneCliArgument("`--v1` or `--v2`".into()));
} else if cli.v1(){
ContractVersion::V1
} else {
ContractVersion::V2
};
let mut contracts = vec![]; let mut contracts = vec![];
if cli.keys() { if cli.keys() {
let keys = PoaContract::read( let keys_contract = PoaContract::read(ContractType::Keys, network, version)?;
ContractType::Keys, contracts.push(keys_contract);
network,
version
)?;
contracts.push(keys);
} }
if cli.threshold() { if cli.threshold() {
let threshold = PoaContract::read( let threshold_contract = PoaContract::read(ContractType::Threshold, network, version)?;
ContractType::Threshold, contracts.push(threshold_contract);
network,
version
)?;
contracts.push(threshold);
} }
if cli.proxy() { if cli.proxy() {
let proxy = PoaContract::read( let proxy_contract = PoaContract::read(ContractType::Proxy, network, version)?;
ContractType::Proxy, contracts.push(proxy_contract);
network,
version
)?;
contracts.push(proxy);
} }
if cli.emission() { if cli.emission() {
let emission_funds = PoaContract::read( let emission_funds = PoaContract::read(ContractType::Emission, network, version)?;
ContractType::Emission,
network,
version,
)?;
contracts.push(emission_funds); contracts.push(emission_funds);
} }
if contracts.is_empty() {
return Err(Error::MustSpecifyAtLeastOneCliArgument(
"`--keys`, `--threshold`, `--proxy`, `--emission`".into()
));
}
let start_block = if cli.multiple_start_blocks_specified() { let start_block = if cli.earliest() {
return Err(Error::MustSpecifyOneCliArgument(
"`--earliest` or `--latest` or `--start-block` or `--tail`".into()
));
} else if cli.earliest() {
StartBlock::Earliest StartBlock::Earliest
} else if cli.latest() { } else if cli.latest() {
StartBlock::Latest StartBlock::Latest
} else if let Some(s) = cli.start_block() { } else if let Some(start_block_str) = cli.start_block() {
let block_number = s.parse().map_err(|_| Error::InvalidStartBlock(s.into()))?; match start_block_str.parse::<u64>() {
StartBlock::Number(block_number) Ok(block_number) => StartBlock::Number(block_number),
} else if let Some(s) = cli.tail() { _ => return Err(Error::InvalidStartBlock(start_block_str.to_string())),
let tail = s.parse().map_err(|_| Error::InvalidTail(s.into()))?; }
StartBlock::Tail(tail) } else if let Some(tail_str) = cli.tail() {
match tail_str.parse::<u64>() {
Ok(tail) => StartBlock::Tail(tail),
_ => return Err(Error::InvalidTail(tail_str.to_string())),
}
} else { } else {
// TODO: use `DEFAULT_START_BLOCK`?
unreachable!(); unreachable!();
}; };
let block_time = if let Some(s) = cli.block_time() { let block_time = if let Some(n_secs_str) = cli.block_time() {
s.parse().map_err(|_| Error::InvalidBlockTime(s.into()))? n_secs_str.parse().map_err(|_| Error::InvalidBlockTime(n_secs_str.to_string()))?
} else { } else {
DEFAULT_BLOCK_TIME_SECS DEFAULT_BLOCK_TIME_SECS
}; };
@ -272,13 +271,7 @@ impl Config {
let email_recipients: Vec<String> = env::var("EMAIL_RECIPIENTS") let email_recipients: Vec<String> = env::var("EMAIL_RECIPIENTS")
.map_err(|_| Error::MissingEnvVar("EMAIL_RECIPIENTS".into()))? .map_err(|_| Error::MissingEnvVar("EMAIL_RECIPIENTS".into()))?
.split(',') .split(',')
.filter_map(|s| { .filter_map(|s| if s.is_empty() { None } else { Some(s.into()) })
if s.is_empty() {
None
} else {
Some(s.into())
}
})
.collect(); .collect();
let smtp_host_domain = if email_notifications { let smtp_host_domain = if email_notifications {
@ -325,7 +318,9 @@ impl Config {
}; };
let notification_limit = if let Some(s) = cli.notification_limit() { let notification_limit = if let Some(s) = cli.notification_limit() {
let limit = s.parse().map_err(|_| Error::InvalidNotificationLimit(s.into()))?; let limit = s
.parse()
.map_err(|_| Error::InvalidNotificationLimit(s.into()))?;
Some(limit) Some(limit)
} else { } else {
None None
@ -334,7 +329,7 @@ impl Config {
let log_emails = cli.log_emails(); let log_emails = cli.log_emails();
let log_to_file = cli.log_to_file(); let log_to_file = cli.log_to_file();
let config = Config { Ok(Config {
network, network,
endpoint, endpoint,
version, version,
@ -351,8 +346,7 @@ impl Config {
notification_limit, notification_limit,
log_emails, log_emails,
log_to_file, log_to_file,
}; })
Ok(config)
} }
} }
@ -360,14 +354,14 @@ impl Config {
mod tests { mod tests {
use std::env; use std::env;
use super::super::tests::setup; use super::{ContractType, ContractVersion, Network, PoaContract};
use super::{ContractType, ContractVersion, PoaContract, Network}; use crate::tests::setup;
const CONTRACT_TYPES: [ContractType; 4] = [ const CONTRACT_TYPES: [ContractType; 4] = [
ContractType::Keys, ContractType::Keys,
ContractType::Threshold, ContractType::Threshold,
ContractType::Proxy, ContractType::Proxy,
ContractType::Emission, ContractType::Emission,
]; ];
const NETWORKS: [Network; 2] = [Network::Sokol, Network::Core]; const NETWORKS: [Network; 2] = [Network::Sokol, Network::Core];
const VERSIONS: [ContractVersion; 2] = [ContractVersion::V1, ContractVersion::V2]; const VERSIONS: [ContractVersion; 2] = [ContractVersion::V1, ContractVersion::V2];
@ -380,7 +374,7 @@ mod tests {
assert!(env::var(&env_var).is_ok()); assert!(env::var(&env_var).is_ok());
for contract_type in CONTRACT_TYPES.iter() { for contract_type in CONTRACT_TYPES.iter() {
for version in VERSIONS.iter() { for version in VERSIONS.iter() {
if *contract_type == ContractType::Emission && *version == ContractVersion::V1 { if contract_type.is_emission() && version.is_v1() {
continue; continue;
} }
let env_var = format!( let env_var = format!(
@ -400,13 +394,12 @@ mod tests {
setup(); setup();
for contract_type in CONTRACT_TYPES.iter() { for contract_type in CONTRACT_TYPES.iter() {
for version in VERSIONS.iter() { for version in VERSIONS.iter() {
if contract_type.is_emission() && version.is_v1() {
continue;
}
for network in NETWORKS.iter() { for network in NETWORKS.iter() {
let res = PoaContract::read(*contract_type, *network, *version); let res = PoaContract::read(*contract_type, *network, *version);
if *contract_type == ContractType::Emission && *version == ContractVersion::V1 { assert!(res.is_ok());
assert!(res.is_err());
} else {
assert!(res.is_ok());
}
} }
} }
} }

View File

@ -41,7 +41,8 @@ fn read_logs_dir() -> Vec<LogFile> {
let path = res.ok()?.path(); let path = res.ok()?.path();
let file_name = path.file_name().unwrap().to_str().unwrap(); let file_name = path.file_name().unwrap().to_str().unwrap();
LogFile::from_file_name(file_name).ok() LogFile::from_file_name(file_name).ok()
}).collect(); })
.collect();
log_files.sort_unstable(); log_files.sort_unstable();
log_files log_files
} }
@ -106,14 +107,12 @@ impl LogFile {
fn create_file(&self) -> File { fn create_file(&self) -> File {
let path = self.path(); let path = self.path();
File::create(&path) File::create(&path).unwrap_or_else(|_| panic!("failed to create log file: {}", path))
.unwrap_or_else(|_| panic!("failed to create log file: {}", path))
} }
fn remove_file(&self) { fn remove_file(&self) {
let path = self.path(); let path = self.path();
remove_file(&path) remove_file(&path).unwrap_or_else(|_| panic!("failed to delete log file: {}", path))
.unwrap_or_else(|_| panic!("failed to delete log file: {}", path))
} }
} }
@ -185,17 +184,27 @@ impl Logger {
} }
pub fn log_ctrlc_pressed(&mut self) { pub fn log_ctrlc_pressed(&mut self) {
warn!(&self.logger, "recieved ctrl-c signal, gracefully shutting down..."); warn!(
&self.logger,
"received ctrl-c signal, gracefully shutting down..."
);
self.increment_log_count(); self.increment_log_count();
} }
pub fn log_no_email_recipients_configured(&mut self) { pub fn log_no_email_recipients_configured(&mut self) {
warn!(&self.logger, "email notifications are enabled, but there are no email recipients"); warn!(
&self.logger,
"email notifications are enabled, but there are no email recipients"
);
self.increment_log_count(); self.increment_log_count();
} }
pub fn log_notification_email_body(&mut self, notif: &Notification) { pub fn log_notification_email_body(&mut self, notif: &Notification) {
info!(&self.logger, "governance notification\n{}", notif.email_text()); info!(
&self.logger,
"governance notification\n{}",
notif.email_text()
);
self.increment_log_count(); self.increment_log_count();
} }

View File

@ -33,7 +33,7 @@ fn load_env_file() {
match dotenv::dotenv() { match dotenv::dotenv() {
Ok(_) => LOADED_ENV_FILE.store(true, Ordering::Relaxed), Ok(_) => LOADED_ENV_FILE.store(true, Ordering::Relaxed),
Err(dotenv::Error::Io(_)) => panic!("could not find .env file"), Err(dotenv::Error::Io(_)) => panic!("could not find .env file"),
_ => panic!("could not parse .env file"), _ => panic!("could not parse .env file"),
}; };
} }
} }
@ -89,13 +89,16 @@ fn main() -> Result<()> {
start_block, start_block,
stop_block, stop_block,
)?; )?;
for log in ballot_created_logs.into_iter() { for log in ballot_created_logs {
let notification = if contract.version == ContractVersion::V1 { let notification = match contract.version {
let voting_state = client.get_voting_state(contract, log.ballot_id)?; ContractVersion::V1 => {
Notification::from_voting_state(&config, log, voting_state) let voting_state = client.get_voting_state(contract, log.ballot_id)?;
} else { Notification::from_voting_state(&config, log, voting_state)
let ballot_info = client.get_ballot_info(contract, log.ballot_id)?; }
Notification::from_ballot_info(&config, log, ballot_info) ContractVersion::V2 => {
let ballot_info = client.get_ballot_info(contract, log.ballot_id)?;
Notification::from_ballot_info(&config, log, ballot_info)
}
}; };
notifications.push(notification); notifications.push(notification);
} }
@ -116,7 +119,10 @@ fn main() -> Result<()> {
} }
} }
logger.lock().unwrap().log_finished_block_window(start_block, stop_block); logger
.lock()
.unwrap()
.log_finished_block_window(start_block, stop_block);
} }
Ok(()) Ok(())

View File

@ -33,29 +33,35 @@ impl<'a> Notification<'a> {
config: &'a Config, config: &'a Config,
log: BallotCreatedLog, log: BallotCreatedLog,
voting_state: VotingState, voting_state: VotingState,
) -> Self ) -> Self {
{ Notification::VotingState {
Notification::VotingState { config, log, voting_state } config,
log,
voting_state,
}
} }
pub fn from_ballot_info( pub fn from_ballot_info(
config: &'a Config, config: &'a Config,
log: BallotCreatedLog, log: BallotCreatedLog,
ballot_info: BallotInfo, ballot_info: BallotInfo,
) -> Self ) -> Self {
{ Notification::BallotInfo {
Notification::BallotInfo { config, log, ballot_info } config,
log,
ballot_info,
}
} }
pub fn email_text(&self) -> String { pub fn email_text(&self) -> String {
format!( format!(
"Network: {:?}\n\ "Network: {:?}\n\
RPC Endpoint: {}\n\ RPC Endpoint: {}\n\
Block Number: {}\n\ Block Number: {}\n\
Contract: {}\n\ Contract: {}\n\
Version: {:?}\n\ Version: {:?}\n\
Ballot ID: {}\n\ Ballot ID: {}\n\
{}\n", {}\n",
self.config().network, self.config().network,
self.config().endpoint, self.config().endpoint,
self.log().block_number, self.log().block_number,
@ -127,12 +133,20 @@ impl<'a> Notifier<'a> {
} else { } else {
None None
}; };
Ok(Notifier { config, emailer, logger, notification_count: 0 }) Ok(Notifier {
config,
emailer,
logger,
notification_count: 0,
})
} }
pub fn notify(&mut self, notif: &Notification) { pub fn notify(&mut self, notif: &Notification) {
if self.config.log_emails { if self.config.log_emails {
self.logger.lock().unwrap().log_notification_email_body(notif); self.logger
.lock()
.unwrap()
.log_notification_email_body(notif);
} else { } else {
self.logger.lock().unwrap().log_notification(notif); self.logger.lock().unwrap().log_notification(notif);
} }
@ -143,10 +157,13 @@ impl<'a> Notifier<'a> {
Err(e) => { Err(e) => {
self.logger.lock().unwrap().log_failed_to_build_email(e); self.logger.lock().unwrap().log_failed_to_build_email(e);
continue; continue;
}, }
}; };
if let Err(e) = self.send_email(email) { if let Err(e) = self.send_email(email) {
self.logger.lock().unwrap().log_failed_to_send_email(recipient, e); self.logger
.lock()
.unwrap()
.log_failed_to_send_email(recipient, e);
} else { } else {
self.logger.lock().unwrap().log_email_sent(recipient); self.logger.lock().unwrap().log_email_sent(recipient);
} }

View File

@ -2,7 +2,6 @@
#![allow(deprecated)] #![allow(deprecated)]
use chrono::{DateTime, NaiveDateTime, Utc}; use chrono::{DateTime, NaiveDateTime, Utc};
use ethabi;
use web3::types::{Address, H256, U256}; use web3::types::{Address, H256, U256};
use crate::error::{Error, Result}; use crate::error::{Error, Result};
@ -129,12 +128,25 @@ impl BallotCreatedLog {
}; };
let ballot_type = match ballot_type { let ballot_type = match ballot_type {
Some(ballot_type) => ballot_type, Some(ballot_type) => ballot_type,
None => return Err(Error::FailedToParseBallotCreatedLog("missing `ballot_type`".into())), None => {
return Err(Error::FailedToParseBallotCreatedLog(
"missing `ballot_type`".to_string(),
))
}
}; };
let creator = match creator { let creator = match creator {
Some(creator) => creator, Some(creator) => creator,
None => return Err(Error::FailedToParseBallotCreatedLog("missing `creator`".into())), None => {
return Err(Error::FailedToParseBallotCreatedLog(
"missing `creator`".to_string(),
))
}
}; };
Ok(BallotCreatedLog { ballot_id, ballot_type, creator, block_number }) Ok(BallotCreatedLog {
ballot_id,
ballot_type,
creator,
block_number,
})
} }
} }

View File

@ -96,7 +96,7 @@ pub struct KeysVotingState {
pub index: U256, pub index: U256,
pub min_threshold_of_voters: U256, pub min_threshold_of_voters: U256,
pub creator: Address, pub creator: Address,
pub memo: String pub memo: String,
} }
impl From<Vec<ethabi::Token>> for KeysVotingState { impl From<Vec<ethabi::Token>> for KeysVotingState {