Applied a handful of cherry-picked rustfmt and clippy changes.
This commit is contained in:
parent
0150df2f8e
commit
a29b43c26c
5
build.rs
5
build.rs
|
@ -16,8 +16,9 @@ fn create_env_file_if_dne() {
|
|||
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`");
|
||||
}
|
||||
fs::copy(sample_env_path, env_path)
|
||||
.unwrap_or_else(|e| panic!("failed to create the `.env` file from `sample.env`: {:?}", e));
|
||||
if let Err(e) = fs::copy(sample_env_path, env_path) {
|
||||
panic!("failed to create the `.env` file from `sample.env`: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,21 @@ use crate::client::RpcClient;
|
|||
use crate::config::{Config, StartBlock};
|
||||
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 = done_sleeping.clone();
|
||||
|
@ -18,16 +32,16 @@ fn sleep_or_ctrlc(n_secs: u64, running: Arc<AtomicBool>) -> Option<()> {
|
|||
done_sleeping.store(true, Ordering::SeqCst);
|
||||
});
|
||||
}
|
||||
loop {
|
||||
while !done_sleeping.load(Ordering::SeqCst) {
|
||||
if !running.load(Ordering::SeqCst) {
|
||||
return None;
|
||||
}
|
||||
if done_sleeping.load(Ordering::SeqCst) {
|
||||
return Some(());
|
||||
return SleepExit::CtrlC;
|
||||
}
|
||||
}
|
||||
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> {
|
||||
client: &'a RpcClient,
|
||||
start_block: u64,
|
||||
|
@ -38,6 +52,16 @@ pub struct 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> {
|
||||
let last_mined_block = client.get_last_mined_block_number()?;
|
||||
let start_block = match config.start_block {
|
||||
|
@ -47,17 +71,19 @@ impl<'a> BlockchainIter<'a> {
|
|||
StartBlock::Tail(tail) => last_mined_block - tail,
|
||||
};
|
||||
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,
|
||||
start_block,
|
||||
stop_block: last_mined_block,
|
||||
on_first_iteration: true,
|
||||
block_time: config.block_time,
|
||||
running,
|
||||
};
|
||||
Ok(bc_iter)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,9 +96,11 @@ impl<'a> Iterator for BlockchainIter<'a> {
|
|||
} else {
|
||||
self.start_block = self.stop_block + 1;
|
||||
while self.start_block >= self.stop_block {
|
||||
sleep_or_ctrlc(self.block_time, self.running.clone())?;
|
||||
match self.client.get_last_mined_block_number() {
|
||||
Ok(last_mined) => self.stop_block = last_mined,
|
||||
if sleep_or_ctrlc(self.block_time, self.running.clone()) == SleepExit::CtrlC {
|
||||
return None;
|
||||
}
|
||||
self.stop_block = match self.client.get_last_mined_block_number() {
|
||||
Ok(last_mined) => last_mined,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Some of `Cli`'s methods are not currently being used.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use clap::{ArgMatches, App};
|
||||
use clap::{App, ArgMatches};
|
||||
|
||||
pub fn parse_cli() -> Cli {
|
||||
let cli_args = App::new("poagov")
|
||||
|
@ -58,6 +58,10 @@ impl Cli {
|
|||
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 {
|
||||
self.0.is_present("v1")
|
||||
}
|
||||
|
|
|
@ -6,8 +6,9 @@ use web3::types::{Address, BlockNumber, Filter, FilterBuilder, U256};
|
|||
|
||||
use crate::config::{ContractType, PoaContract};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::response::{v1, v2};
|
||||
use crate::response::common::BallotCreatedLog;
|
||||
use crate::response::v1;
|
||||
use crate::response::v2;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RpcMethod {
|
||||
|
@ -43,8 +44,7 @@ impl RpcClient {
|
|||
&self,
|
||||
method: RpcMethod,
|
||||
params: Vec<json::Value>,
|
||||
) -> Result<reqwest::Request>
|
||||
{
|
||||
) -> Result<reqwest::Request> {
|
||||
let method_call = json_rpc::types::request::MethodCall {
|
||||
jsonrpc: Some(json_rpc::types::version::Version::V2),
|
||||
method: method.into(),
|
||||
|
@ -60,7 +60,8 @@ impl RpcClient {
|
|||
}
|
||||
|
||||
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)
|
||||
.map_err(|e| Error::RequestFailed(e))?
|
||||
.json()
|
||||
|
@ -68,7 +69,9 @@ impl RpcClient {
|
|||
if let json_rpc::types::response::Response::Single(resp_status) = resp {
|
||||
match resp_status {
|
||||
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");
|
||||
|
@ -97,8 +100,7 @@ impl RpcClient {
|
|||
contract: &PoaContract,
|
||||
start: BlockNumber,
|
||||
stop: BlockNumber,
|
||||
) -> Result<Vec<BallotCreatedLog>>
|
||||
{
|
||||
) -> Result<Vec<BallotCreatedLog>> {
|
||||
let event = contract.event("BallotCreated");
|
||||
let event_sig = event.signature();
|
||||
let filter = FilterBuilder::default()
|
||||
|
@ -110,9 +112,15 @@ impl RpcClient {
|
|||
self.get_logs(filter)?
|
||||
.into_iter()
|
||||
.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 ethabi_log = event.parse_log(raw_log)
|
||||
let ethabi_log = event
|
||||
.parse_log(raw_log)
|
||||
.map_err(|e| Error::FailedToParseRawLogToLog(e))?;
|
||||
BallotCreatedLog::from_ethabi_log(ethabi_log, block_number.unwrap())
|
||||
})
|
||||
|
@ -120,7 +128,11 @@ impl RpcClient {
|
|||
}
|
||||
|
||||
/// 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 tokens = vec![ethabi::Token::Uint(ballot_id)];
|
||||
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
|
||||
// `.getBallotInfo()` with `Address::zero()` for the `votingKey` works (we don't care if
|
||||
// `votingKey` has voted yet).
|
||||
pub fn get_ballot_info(&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> {
|
||||
pub fn get_ballot_info(
|
||||
&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 mut tokens = vec![ethabi::Token::Uint(ballot_id)];
|
||||
|
@ -208,15 +224,16 @@ mod tests {
|
|||
|
||||
use web3::types::BlockNumber;
|
||||
|
||||
use crate::tests::setup;
|
||||
use crate::config::{ContractType, ContractVersion, Network, PoaContract};
|
||||
use super::RpcClient;
|
||||
use crate::config::{ContractType, ContractVersion, Network, PoaContract};
|
||||
use crate::tests::setup;
|
||||
|
||||
#[test]
|
||||
fn test_get_last_mined_block() {
|
||||
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 res = client.get_last_mined_block_number();
|
||||
println!("\nsokol last mined block number => {:?}", res);
|
||||
|
@ -232,18 +249,13 @@ mod tests {
|
|||
#[test]
|
||||
fn test_get_ballot_created_logs() {
|
||||
setup();
|
||||
let contract = PoaContract::read(
|
||||
ContractType::Keys,
|
||||
Network::Sokol,
|
||||
ContractVersion::V1,
|
||||
).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 contract = PoaContract::read(ContractType::Keys, Network::Sokol, ContractVersion::V1)
|
||||
.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 res = client.get_ballot_created_logs(
|
||||
&contract,
|
||||
BlockNumber::Earliest,
|
||||
BlockNumber::Latest,
|
||||
);
|
||||
let res =
|
||||
client.get_ballot_created_logs(&contract, BlockNumber::Earliest, BlockNumber::Latest);
|
||||
println!("{:#?}", res);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
@ -273,12 +285,10 @@ mod tests {
|
|||
#[test]
|
||||
fn test_get_voting_state() {
|
||||
setup();
|
||||
let contract = PoaContract::read(
|
||||
ContractType::Threshold,
|
||||
Network::Sokol,
|
||||
ContractVersion::V1
|
||||
).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 contract = PoaContract::read(ContractType::Threshold, Network::Sokol, ContractVersion::V1)
|
||||
.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 res = client.get_voting_state(&contract, 2.into());
|
||||
println!("{:#?}", res);
|
||||
|
|
189
src/config.rs
189
src/config.rs
|
@ -50,6 +50,10 @@ impl From<BallotType> for ContractType {
|
|||
}
|
||||
|
||||
impl ContractType {
|
||||
fn is_emission(&self) -> bool {
|
||||
*self == ContractType::Emission
|
||||
}
|
||||
|
||||
fn uppercase(&self) -> &str {
|
||||
match self {
|
||||
ContractType::Keys => "KEYS",
|
||||
|
@ -76,6 +80,10 @@ pub enum ContractVersion {
|
|||
}
|
||||
|
||||
impl ContractVersion {
|
||||
fn is_v1(&self) -> bool {
|
||||
*self == ContractVersion::V1
|
||||
}
|
||||
|
||||
fn lowercase(&self) -> &str {
|
||||
match self {
|
||||
ContractVersion::V1 => "v1",
|
||||
|
@ -103,46 +111,44 @@ impl Debug for PoaContract {
|
|||
}
|
||||
|
||||
impl PoaContract {
|
||||
fn new(
|
||||
kind: ContractType,
|
||||
version: ContractVersion,
|
||||
addr: Address,
|
||||
abi: ethabi::Contract,
|
||||
) -> Self {
|
||||
PoaContract { kind, version, addr, abi }
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
contract_type: ContractType,
|
||||
network: Network,
|
||||
version: ContractVersion,
|
||||
) -> Result<Self>
|
||||
{
|
||||
if contract_type == ContractType::Emission && version == ContractVersion::V1 {
|
||||
) -> Result<Self> {
|
||||
// Exit quickly if we know that the contract does not exist.
|
||||
if contract_type.is_emission() && version.is_v1() {
|
||||
return Err(Error::EmissionFundsV1ContractDoesNotExist);
|
||||
}
|
||||
|
||||
let addr_env_var = format!(
|
||||
let env_var = format!(
|
||||
"{}_CONTRACT_ADDRESS_{}_{:?}",
|
||||
contract_type.uppercase(),
|
||||
network.uppercase(),
|
||||
version,
|
||||
version
|
||||
);
|
||||
|
||||
let addr = if let Ok(s) = env::var(&addr_env_var) {
|
||||
match Address::from_str(s.trim_left_matches("0x")) {
|
||||
Ok(addr) => addr,
|
||||
Err(_) => return Err(Error::InvalidContractAddr(s.into())),
|
||||
}
|
||||
} else {
|
||||
return Err(Error::MissingEnvVar(addr_env_var));
|
||||
};
|
||||
let contract_addr_str = env::var(&env_var).map_err(|_| Error::MissingEnvVar(env_var))?;
|
||||
let contract_addr = Address::from_str(contract_addr_str.trim_left_matches("0x"))
|
||||
.map_err(|_| Error::InvalidContractAddr(contract_addr_str.to_string()))?;
|
||||
|
||||
let abi_path = format!(
|
||||
"abis/{}/{}",
|
||||
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)
|
||||
.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)
|
||||
Ok(PoaContract::new(contract_type, version, contract_addr, abi))
|
||||
}
|
||||
|
||||
pub fn event(&self, event: &str) -> Event {
|
||||
|
@ -184,85 +190,78 @@ pub struct Config {
|
|||
|
||||
impl Config {
|
||||
pub fn new(cli: &Cli) -> Result<Self> {
|
||||
let network = if cli.core() == cli.sokol() {
|
||||
return Err(Error::MustSpecifyOneCliArgument("`--core` or `--sokol`".into()));
|
||||
} else if cli.core() {
|
||||
if cli.core() == cli.sokol() {
|
||||
return Err(Error::MustSpecifyOneCliArgument("--core, --sokol".to_string()));
|
||||
}
|
||||
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
|
||||
} else {
|
||||
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(&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![];
|
||||
if cli.keys() {
|
||||
let keys = PoaContract::read(
|
||||
ContractType::Keys,
|
||||
network,
|
||||
version
|
||||
)?;
|
||||
contracts.push(keys);
|
||||
let keys_contract = PoaContract::read(ContractType::Keys, network, version)?;
|
||||
contracts.push(keys_contract);
|
||||
}
|
||||
if cli.threshold() {
|
||||
let threshold = PoaContract::read(
|
||||
ContractType::Threshold,
|
||||
network,
|
||||
version
|
||||
)?;
|
||||
contracts.push(threshold);
|
||||
let threshold_contract = PoaContract::read(ContractType::Threshold, network, version)?;
|
||||
contracts.push(threshold_contract);
|
||||
}
|
||||
if cli.proxy() {
|
||||
let proxy = PoaContract::read(
|
||||
ContractType::Proxy,
|
||||
network,
|
||||
version
|
||||
)?;
|
||||
contracts.push(proxy);
|
||||
let proxy_contract = PoaContract::read(ContractType::Proxy, network, version)?;
|
||||
contracts.push(proxy_contract);
|
||||
}
|
||||
if cli.emission() {
|
||||
let emission_funds = PoaContract::read(
|
||||
ContractType::Emission,
|
||||
network,
|
||||
version,
|
||||
)?;
|
||||
let emission_funds = PoaContract::read(ContractType::Emission, network, version)?;
|
||||
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() {
|
||||
return Err(Error::MustSpecifyOneCliArgument(
|
||||
"`--earliest` or `--latest` or `--start-block` or `--tail`".into()
|
||||
));
|
||||
} else if cli.earliest() {
|
||||
let start_block = if cli.earliest() {
|
||||
StartBlock::Earliest
|
||||
} else if cli.latest() {
|
||||
StartBlock::Latest
|
||||
} else if let Some(s) = cli.start_block() {
|
||||
let block_number = s.parse().map_err(|_| Error::InvalidStartBlock(s.into()))?;
|
||||
StartBlock::Number(block_number)
|
||||
} else if let Some(s) = cli.tail() {
|
||||
let tail = s.parse().map_err(|_| Error::InvalidTail(s.into()))?;
|
||||
StartBlock::Tail(tail)
|
||||
} else if let Some(start_block_str) = cli.start_block() {
|
||||
match start_block_str.parse::<u64>() {
|
||||
Ok(block_number) => StartBlock::Number(block_number),
|
||||
_ => return Err(Error::InvalidStartBlock(start_block_str.to_string())),
|
||||
}
|
||||
} 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 {
|
||||
// TODO: use `DEFAULT_START_BLOCK`?
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let block_time = if let Some(s) = cli.block_time() {
|
||||
s.parse().map_err(|_| Error::InvalidBlockTime(s.into()))?
|
||||
let block_time = if let Some(n_secs_str) = cli.block_time() {
|
||||
n_secs_str.parse().map_err(|_| Error::InvalidBlockTime(n_secs_str.to_string()))?
|
||||
} else {
|
||||
DEFAULT_BLOCK_TIME_SECS
|
||||
};
|
||||
|
@ -272,13 +271,7 @@ impl Config {
|
|||
let email_recipients: Vec<String> = env::var("EMAIL_RECIPIENTS")
|
||||
.map_err(|_| Error::MissingEnvVar("EMAIL_RECIPIENTS".into()))?
|
||||
.split(',')
|
||||
.filter_map(|s| {
|
||||
if s.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(s.into())
|
||||
}
|
||||
})
|
||||
.filter_map(|s| if s.is_empty() { None } else { Some(s.into()) })
|
||||
.collect();
|
||||
|
||||
let smtp_host_domain = if email_notifications {
|
||||
|
@ -325,7 +318,9 @@ impl Config {
|
|||
};
|
||||
|
||||
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)
|
||||
} else {
|
||||
None
|
||||
|
@ -334,7 +329,7 @@ impl Config {
|
|||
let log_emails = cli.log_emails();
|
||||
let log_to_file = cli.log_to_file();
|
||||
|
||||
let config = Config {
|
||||
Ok(Config {
|
||||
network,
|
||||
endpoint,
|
||||
version,
|
||||
|
@ -351,8 +346,7 @@ impl Config {
|
|||
notification_limit,
|
||||
log_emails,
|
||||
log_to_file,
|
||||
};
|
||||
Ok(config)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -360,14 +354,14 @@ impl Config {
|
|||
mod tests {
|
||||
use std::env;
|
||||
|
||||
use super::super::tests::setup;
|
||||
use super::{ContractType, ContractVersion, PoaContract, Network};
|
||||
use super::{ContractType, ContractVersion, Network, PoaContract};
|
||||
use crate::tests::setup;
|
||||
|
||||
const CONTRACT_TYPES: [ContractType; 4] = [
|
||||
ContractType::Keys,
|
||||
ContractType::Threshold,
|
||||
ContractType::Proxy,
|
||||
ContractType::Emission,
|
||||
ContractType::Keys,
|
||||
ContractType::Threshold,
|
||||
ContractType::Proxy,
|
||||
ContractType::Emission,
|
||||
];
|
||||
const NETWORKS: [Network; 2] = [Network::Sokol, Network::Core];
|
||||
const VERSIONS: [ContractVersion; 2] = [ContractVersion::V1, ContractVersion::V2];
|
||||
|
@ -380,7 +374,7 @@ mod tests {
|
|||
assert!(env::var(&env_var).is_ok());
|
||||
for contract_type in CONTRACT_TYPES.iter() {
|
||||
for version in VERSIONS.iter() {
|
||||
if *contract_type == ContractType::Emission && *version == ContractVersion::V1 {
|
||||
if contract_type.is_emission() && version.is_v1() {
|
||||
continue;
|
||||
}
|
||||
let env_var = format!(
|
||||
|
@ -400,13 +394,12 @@ mod tests {
|
|||
setup();
|
||||
for contract_type in CONTRACT_TYPES.iter() {
|
||||
for version in VERSIONS.iter() {
|
||||
if contract_type.is_emission() && version.is_v1() {
|
||||
continue;
|
||||
}
|
||||
for network in NETWORKS.iter() {
|
||||
let res = PoaContract::read(*contract_type, *network, *version);
|
||||
if *contract_type == ContractType::Emission && *version == ContractVersion::V1 {
|
||||
assert!(res.is_err());
|
||||
} else {
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,8 @@ fn read_logs_dir() -> Vec<LogFile> {
|
|||
let path = res.ok()?.path();
|
||||
let file_name = path.file_name().unwrap().to_str().unwrap();
|
||||
LogFile::from_file_name(file_name).ok()
|
||||
}).collect();
|
||||
})
|
||||
.collect();
|
||||
log_files.sort_unstable();
|
||||
log_files
|
||||
}
|
||||
|
@ -106,14 +107,12 @@ impl LogFile {
|
|||
|
||||
fn create_file(&self) -> File {
|
||||
let path = self.path();
|
||||
File::create(&path)
|
||||
.unwrap_or_else(|_| panic!("failed to create log file: {}", path))
|
||||
File::create(&path).unwrap_or_else(|_| panic!("failed to create log file: {}", path))
|
||||
}
|
||||
|
||||
fn remove_file(&self) {
|
||||
let path = self.path();
|
||||
remove_file(&path)
|
||||
.unwrap_or_else(|_| panic!("failed to delete log file: {}", path))
|
||||
remove_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) {
|
||||
warn!(&self.logger, "recieved ctrl-c signal, gracefully shutting down...");
|
||||
warn!(
|
||||
&self.logger,
|
||||
"received ctrl-c signal, gracefully shutting down..."
|
||||
);
|
||||
self.increment_log_count();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -33,7 +33,7 @@ fn load_env_file() {
|
|||
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"),
|
||||
_ => panic!("could not parse .env file"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -89,13 +89,16 @@ fn main() -> Result<()> {
|
|||
start_block,
|
||||
stop_block,
|
||||
)?;
|
||||
for log in ballot_created_logs.into_iter() {
|
||||
let notification = if contract.version == ContractVersion::V1 {
|
||||
let voting_state = client.get_voting_state(contract, log.ballot_id)?;
|
||||
Notification::from_voting_state(&config, log, voting_state)
|
||||
} else {
|
||||
let ballot_info = client.get_ballot_info(contract, log.ballot_id)?;
|
||||
Notification::from_ballot_info(&config, log, ballot_info)
|
||||
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);
|
||||
}
|
||||
|
@ -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(())
|
||||
|
|
|
@ -33,29 +33,35 @@ impl<'a> Notification<'a> {
|
|||
config: &'a Config,
|
||||
log: BallotCreatedLog,
|
||||
voting_state: VotingState,
|
||||
) -> Self
|
||||
{
|
||||
Notification::VotingState { config, log, voting_state }
|
||||
) -> Self {
|
||||
Notification::VotingState {
|
||||
config,
|
||||
log,
|
||||
voting_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_ballot_info(
|
||||
config: &'a Config,
|
||||
log: BallotCreatedLog,
|
||||
ballot_info: BallotInfo,
|
||||
) -> Self
|
||||
{
|
||||
Notification::BallotInfo { config, log, ballot_info }
|
||||
) -> Self {
|
||||
Notification::BallotInfo {
|
||||
config,
|
||||
log,
|
||||
ballot_info,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn email_text(&self) -> String {
|
||||
format!(
|
||||
"Network: {:?}\n\
|
||||
RPC Endpoint: {}\n\
|
||||
Block Number: {}\n\
|
||||
Contract: {}\n\
|
||||
Version: {:?}\n\
|
||||
Ballot ID: {}\n\
|
||||
{}\n",
|
||||
RPC Endpoint: {}\n\
|
||||
Block Number: {}\n\
|
||||
Contract: {}\n\
|
||||
Version: {:?}\n\
|
||||
Ballot ID: {}\n\
|
||||
{}\n",
|
||||
self.config().network,
|
||||
self.config().endpoint,
|
||||
self.log().block_number,
|
||||
|
@ -127,12 +133,20 @@ impl<'a> Notifier<'a> {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Notifier { config, emailer, logger, notification_count: 0 })
|
||||
Ok(Notifier {
|
||||
config,
|
||||
emailer,
|
||||
logger,
|
||||
notification_count: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn notify(&mut self, notif: &Notification) {
|
||||
if self.config.log_emails {
|
||||
self.logger.lock().unwrap().log_notification_email_body(notif);
|
||||
self.logger
|
||||
.lock()
|
||||
.unwrap()
|
||||
.log_notification_email_body(notif);
|
||||
} else {
|
||||
self.logger.lock().unwrap().log_notification(notif);
|
||||
}
|
||||
|
@ -143,10 +157,13 @@ impl<'a> Notifier<'a> {
|
|||
Err(e) => {
|
||||
self.logger.lock().unwrap().log_failed_to_build_email(e);
|
||||
continue;
|
||||
},
|
||||
}
|
||||
};
|
||||
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 {
|
||||
self.logger.lock().unwrap().log_email_sent(recipient);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#![allow(deprecated)]
|
||||
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use ethabi;
|
||||
use web3::types::{Address, H256, U256};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
@ -129,12 +128,25 @@ impl BallotCreatedLog {
|
|||
};
|
||||
let ballot_type = match 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 {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ pub struct KeysVotingState {
|
|||
pub index: U256,
|
||||
pub min_threshold_of_voters: U256,
|
||||
pub creator: Address,
|
||||
pub memo: String
|
||||
pub memo: String,
|
||||
}
|
||||
|
||||
impl From<Vec<ethabi::Token>> for KeysVotingState {
|
||||
|
|
Loading…
Reference in New Issue