diff --git a/README.md b/README.md index 811d275..7f0bd89 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,23 @@ # `poa-governance-notifications` -A tool to monitor a POA Network blockchain for -[governance events](https://github.com/poanetwork/wiki/wiki/Governance-Overview). +A CLI tool for monitoring a blockchain for POA Network governance ballots. This tool can be used to +monitor _any_ chain that uses POA Network's governance contracts. + +More info regarding governance can be found in +[POA Network's Wiki](https://github.com/poanetwork/wiki/wiki/Governance-Overview). + +POA Network's governance contracts can be found in the +[`poa-network-consensus-contracts` repo](https://github.com/poanetwork/poa-network-consensus-contracts/tree/master/contracts), +all Solidity files prefixed with "Voting" are classified as a "governance contract". The `poagov` command line tool is distributed as a binary for Linux and -OSX; it can also be built from source for both platforms. +OSX. The `poagov` binary can be built from source for both OSX and Linux using the code in this repo. -You can find the source code for the currently deployed governance contracts -[here](https://github.com/poanetwork/poa-network-consensus-contracts/tree/master/contracts). +### Downloading the `poagov` Binary -You can find the addresses for governance contracts currently deployed to Core -[here](https://github.com/poanetwork/poa-chain-spec/blob/core/contracts.json) -and Sokol -[here](https://github.com/poanetwork/poa-chain-spec/blob/sokol/contracts.json). - -# Installing the `poagov` Binary - -*Note:* the `poagov` binary requires libssl to be installed prior to -usage, if you do not have libssl installed, go to the "Requires libssl" +*Note:* the `poagov` binary requires `libssl` to be installed prior to +usage, if you do not have `libssl` installed, go to the "Requires libssl" section in this README to find out how to download it. On Debian/Ubuntu: @@ -41,34 +40,26 @@ On OSX: $ ./poagov --help -# Building `poagov` from Source +### Building `poagov` from Source -To build the `poagov` CLI tool, run the following: +To build the `poagov` CLI tool from source, clone this repo and run: - $ git clone https://github.com/poanetwork/poa-governance-notifications.git - $ cd poa-governance-notifications $ cargo build --release `poagov` can be built with Rust 1.31.0-stable or later and requires `libssl`; -see the following "Requires libssl" section for more information. +see the "Requires `libssl`" section in this README for more information. -### Testing +##### Testing `poagov` You can run `poagov`'s tests to ensure that everything is working properly: $ cargo test --release -The test suite will verify: that the required env-vars are found in the `.env` -file, that each network's JSON-RPC server can be reached, and that each -contract ABI can be loaded. - -# Requires `libssl` +### Running `poagov` Requires `libssl` SMTP over TLS requires that you have a native TLS library installed on your machine, the preferred library for Linux and OSX is OpenSSL >= 1.0.1, -otherwise known as `libssl` (you will need more than just the OpenSSL -binary that you may or may not already have installed at -`/usr/bin/openssl`). +otherwise known as `libssl`. If running `cargo build --release` panics with an error like: @@ -84,7 +75,7 @@ To install `libssl` on Debian/Ubuntu run the following: $ sudo apt update $ sudo apt-get install -y pkg-config libssl-dev -To install libssl on MacOS run the following: +To install `libssl` on MacOS run the following: $ brew update $ brew install openssl @@ -111,7 +102,7 @@ The above solution comes from the linked Stack Overflow thread. More information on common issues encountered while installing the `openssl` Rust crate can be found [here](https://crates.io/crates/openssl). -# Usage +### Usage Once you have built or downloaded `poagov`, you can print out the CLI usage by running: @@ -128,68 +119,88 @@ running: poagov [FLAGS] [OPTIONS] FLAGS: - --core monitor voting contracts deployed to the Core network - --sokol monitor voting contracts deployed to the Sokol network - --v1 monitors the v1 voting contracts - --v2 monitors the v2 voting contracts - -k, --keys monitors the blockchain for ballots to change keys - -p, --proxy monitors the blockchain for ballots to change the proxy address - -t, --threshold monitors the blockchain for ballots to change the minimum threshold - -e, --emission monitors the blockchain for ballots to manage emission funds - --earliest begin monitoring for governance events starting at the first block in the blockchain - --latest begin monitoring for governance events starting at the last block mined - --email enables email notifications (SMTP configurations must be set in your `.env` file) - --log-emails logs each notification's email body; does not require the --email flag to be set - --log-file logs are written to files in the ./logs directory, log files are rotated when they reach a size of 4MB - -h, --help prints help information - -V, --version prints version information + --core Monitors POA Network's Core Network for governance ballots + --sokol Monitors POA Network's Sokol network for governance ballots + --xdai Monitors the xDai Network for governance ballots + + --v1 Monitors the v1 governance contracts + --v2 [default] Monitors the v2 governance contracts, if no contract version CLI argument is given by + + -k, --keys Monitors the blockchain for ballots to change keys + -t, --threshold Monitors the blockchain for ballots to change the minimum threshold + the user, we set this CLI flag + -p, --proxy Monitors the blockchain for ballots to change the proxy address + -e, --emission Monitors the blockchain for ballots to manage emission funds + + --earliest Monitor for governance events starting at the blockchain's first block + --latest Monitor for governance events starting at the blockchain's most recently mined block + + --email Enables email notifications (SMTP configuration options must be set in your `.env` file) + --log-emails Logs the full email body for each notification generated, this option does not require the + `--email` flag to be set + --log-file Logs are written to files in the ./logs directory, logs are rotated chronologically across 3 + files, each file has a max size of 4MB + + -h, --help Prints help information + -V, --version Prints version information OPTIONS: - --block-time the average number of seconds it takes to mine a new block - -n, --limit shutdown `poagov` after this many notifications have been generated, useful when testing - --start start monitoring for governance events at this block (inclusive) - --tail start monitoring for governance events for the `n` blocks prior to the last block mined + --block-time The average number of seconds it takes to mine a new block + -n, --limit Stops `poagov` after this many notifications have been generated (this option can be + useful when testing `poagov`) + + --start Start monitoring for governance events at this block (inclusive) + --tail Start monitoring for governance events for the `n` blocks prior to the last mined block Hitting `[ctrl-c]` while `poagov` is running will cause the process to gracefully shutdown. -### Required Arguments +##### Required CLI Arguments -Each time you run `poagov`, four CLI arguments are required: +Each time you run `poagov`, three CLI arguments are required: -1. The chain (specify only one): `--core`, `--sokol`. -2. The contracts to monitor (specify at least one): `--keys`, `--threshold`, `--proxy`, `--emission`. -2. The hardfork version (specify only one): `--v1`, `--v2`. -4. The block in the chain from where to start monitoring (specify only one): `--earliest`, `--latest`, `--start=`, `--tail=`. +1. The chain (specify only one): `--core`, `--sokol`, `--xdai`. +2. The governance ballots to monitor (specify at least one): `--keys`, `--threshold`, `--proxy`, `--emission`. +3. The block in the chain from where to start monitoring (specify only one): `--earliest`, `--latest`, `--start=`, `--tail=`. -### Notes on the hardfork version options `--v1` and `--v2` +##### Notes on the Hardfork Version CLI Options: `--v1` and `--v2` `--v1` indicates that you want to monitor for governance events prior to the Sokol and Core hardforks that will occur in September-2018 and November-2018 respectively. -`--v2` indicates that you want to monitor for governance events that occured +`--v2` indicates that you want to monitor for governance events that occurred after the above hardfork dates. -- More information regarding the planned hardforks for the Sokol and Core +We default to `--v2` being set as it will monitor the currently deployed contract. + +- More information regarding the planned hardforks for the POA Sokol and Core chains in September and November 2018 can be found [here](https://medium.com/poa-network/poa-network-news-and-updates-36-2e6e00550c15). ### Optional Arguments -Providing the `--email` flag will enable governance notification emails. To use -this option, you must first configure SMTP in your `.env`. +Providing the `--v1` flag will tell `poagov` to look for ballots corresponding +to the hardfork #1 governance contracts. The hardfork #1 contracts are not +currently being by POA Network and not new governance notifications will be +generated, however you can use `poagov` to view all past `--v1` ballots that +have occurred using: + + $ poagov <--core, --sokol> --v1 --earliest -ktp + +Providing the `--email` flag will enable governance notification via email. To +use this option, you must first configure SMTP in your `.env` file. Providing the `--block-time=` will set how often `poagov` will query the blockchain for new governance events. Defaults to 30 seconds. Providing the `--log-emails` flag will print the full text for a notification -email to stderr when governance events are found. When this option is set, +email to `stderr` when governance events are found. When this option is set, email text will be logged regardless of whether or not the `--email` flag is set. -Setting the `--log-file` flag will write logs to a file in the `./logs/` +Setting the `--log-file` flag will write logs to a file in the `logs/` directory. Logs are rotated chronologically across three files. Once the -`logs` directory has reached its max number of files, the oldest log file will +`logs/` directory has reached its max number of files, the oldest log file will be deleted to make room for the next log file. Log files have a max size of 4MB; the log files will rotated once the current log file has reached the max file size. @@ -197,7 +208,7 @@ file size. Setting the `--limit=` option will cause `poagov` to stop once `value` number of notifications have been generated. This option is useful when testing. -# Setting up the `.env` File +### Setting up the `.env` File When the `poagov` CLI tool is run, the process' environment variables are loaded via an `.env` file. The `.env` file contains configuration variables @@ -217,40 +228,40 @@ to run `poagov`. If you wish to enable email notifications, you must add the required SMTP config values to your `.env` file. See the "Setting up Email Notifications" section for details. -### Setting up Email Notifications +##### Setting up Email Notifications In order to enable email notifications, you must change the name of the `sample.env` file to `.env`. Then, you must add values for the following SMTP config options in your `.env` file: - EMAIL_RECIPIENTS= SMTP_HOST_DOMAIN= SMTP_PORT= SMTP_USERNAME= SMTP_PASSWORD= OUTGOING_EMAIL_ADDRESS= + EMAIL_RECIPIENTS= -Add a comma-separated list of email address to the "VALIDATORS" config -option in your .env file. These addresses will be sent emails when `poagov` -encounters governance events on the POA blockchain. +Add a comma-separated list of email address to the `EMAIL_RECIPIENTS` config +option in your `.env` file. These addresses will be sent emails when `poagov` +encounters new ballots. -*Note* `poagov` forces SMTP email notifcations to be sent over an encrypted -channel, if your SMTP Host does not support TLS or STARTTLS, `poagov` will -panic. You may notice that we default `SMTP_PORT` to port 587 for STARTTLS, -but you may use port 465 for TLS, or any other port that your outgoing -email server is lisening for secure connections. If you require unencrypted -SMTP, submit an issue and I can add it. +*Note* `poagov` forces SMTP email notifcations to be sent over TLS/STARTTLS, if +your SMTP Host does not support TLS or STARTTLS, `poagov` will `panic!`. + +You may notice that we default `SMTP_PORT` to port 587 for STARTTLS, but you +may use any port for which your outgoing email server is listening; port 465 is +commonly used for TLS. Your SMTP configuration should look something like the following: - EMAIL_RECIPIENTS=alice@poa.network,bob@poa.network SMTP_HOST_DOMAIN=mail.riseup.net SMTP_PORT=587 SMTP_USERNAME=evariste_galois SMTP_PASSWORD='finteFIELDS#$!' OUTGOING_EMAIL_ADDRESS=evariste_galois@riseup.net + EMAIL_RECIPIENTS=alice@poa.network,bob@poa.network -# An Explained Example +### An Explained Example $ poagov --sokol --v1 -kt --earliest --email --log-emails --limit=1 @@ -263,14 +274,13 @@ Your SMTP configuration should look something like the following: - `--log-emails` for each governance notification generated, log the corresponding email body. - `--limit=1` stop running `poagov` after one ballot notification has been generated. -# Logs +### Logs -Logs are output to `stderr` unless the `--log-file` CLI flag is set. Events -that are logged include: the generation of governance notifications, sending an -email successesfully or failing to send an email, aned what range of blocks -from the chain have been successfully monitored for governance events. -Optionally, you can log the email body for each governance notification -generated by setting the `--log-emails` CLI flag. +Logs are written to `stderr` by default; if the `--log-file` CLI flag is set, +then logs will be written to a file in the `logs.` directory. Logged +information includes: the generation of governance notifications, sending an +email successfully, failing to send an email, blocks that we have finished +monitoring, email bodies generated (using the `--log-emails` CLI option). The following is an example command with its corresponding logs: diff --git a/sample.env b/sample.env index fcee54b..856a9b7 100644 --- a/sample.env +++ b/sample.env @@ -27,17 +27,6 @@ THRESHOLD_CONTRACT_ADDRESS_SOKOL_V1=0x700db8ba3128087f3b23f60de4bc3179bafa467d PROXY_CONTRACT_ADDRESS_CORE_V1=0x9c8a06f0197ee718cd820adeb48a88ea2a9b5c48 PROXY_CONTRACT_ADDRESS_SOKOL_V1=0x0aa4a75549757a90f62f88b3b96b69bead2db0ff -# TODO: rename env-vars to _CONTRACT_ADDRESS__: -# -# KEYS_CONTRACT_ADDRESS_V1_CORE= -# KEYS_CONTRACT_ADDRESS_V1_SOKOL= -# -# THRESHOLD_CONTRACT_ADDRESS_V1_CORE= -# THRESHOLD_CONTRACT_ADDRESS_V1_SOKOL= -# -# PROXY_CONTRACT_ADDRESS_V1_CORE= -# PROXY_CONTRACT_ADDRESS_V1_SOKOL= - # ------------------------------------------------------------------------ # V2 Governance Contract Addresses Deployed on the Core, Sokol, and xDai Networks # ------------------------------------------------------------------------ @@ -61,7 +50,6 @@ PROXY_CONTRACT_ADDRESS_CORE_V2=0x468758926c796722d85bded792d1831f0839caa6 PROXY_CONTRACT_ADDRESS_SOKOL_V2=0x604cdc518f3eb0446e15fc05a22923c82d8a8e21 PROXY_CONTRACT_ADDRESS_XDAI_V2=0x96bcea97bd6cbf1cc6d637796c5c3a7a642bc8fc -# TODO: change this env-var name to "EMISSION_": EMISSION_FUNDS_CONTRACT_ADDRESS_CORE_V2=0x7e9b90b22cdd1f6aa206f0d852ac96212217d60e EMISSION_FUNDS_CONTRACT_ADDRESS_SOKOL_V2=0x7cfa6f2c0d032f9dde652996e989a4d385b8b9d7 EMISSION_FUNDS_CONTRACT_ADDRESS_XDAI_V2=0x918092ab6f2b6f24b82aeaf797566a222dea4d28 diff --git a/src/cli.rs b/src/cli.rs index c6b5986..2c65f6f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -8,23 +8,24 @@ pub fn parse_cli() -> Cli { .version("1.0.0") .about("Monitors a POA Network blockchain for governance events.") .args_from_usage( - "[core] --core 'monitor voting contracts deployed to the Core network' - [sokol] --sokol 'monitor voting contracts deployed to the Sokol network' - [keys] -k --keys 'monitors the blockchain for ballots to change keys' - [threshold] -t --threshold 'monitors the blockchain for ballots to change the minimum threshold' - [proxy] -p --proxy 'monitors the blockchain for ballots to change the proxy address' - [emission] -e --emission 'monitors the blockchain for ballots to manage emission funds' - [v1] --v1 'monitors the v1 voting contracts' - [v2] --v2 'monitors the v2 voting contracts' - [earliest] --earliest 'begin monitoring for governance events starting at the first block in the blockchain' - [latest] --latest 'begin monitoring for governance events starting at the last block mined' - [start_block] --start [value] 'start monitoring for governance events at this block (inclusive)' - [tail] --tail [value] 'start monitoring for governance events for the `n` blocks prior to the last block mined' - [email] --email 'enables email notifications (SMTP configurations must be set in your `.env` file)' - [block_time] --block-time [value] 'the average number of seconds it takes to mine a new block' - [notification_limit] -n --limit [value] 'shutdown `poagov` after this many notifications have been generated' - [log_emails] --log-emails 'logs each notification's email body; does not require the --email flag to be set' - [log_to_file] --log-file 'logs are written to files in the ./logs directory, logs are rotated chronologically across 3 files, each file has a max size of 8MB'" + "[core] --core 'Monitors POA Network's Core Network for governance ballots' + [sokol] --sokol 'Monitors POA Network's Sokol network for governance ballots' + [xdai] --xdai 'Monitors the xDai Network for governance ballots' + [keys] -k --keys 'Monitors the blockchain for ballots to change keys' + [threshold] -t --threshold 'Monitors the blockchain for ballots to change the minimum threshold' + [proxy] -p --proxy 'Monitors the blockchain for ballots to change the proxy address' + [emission] -e --emission 'Monitors the blockchain for ballots to manage emission funds' + [v1] --v1 'Monitors the v1 governance contracts' + [v2] --v2 '[default] Monitors the v2 governance contracts, if no contract version CLI argument is given by the user, we set this CLI flag' + [earliest] --earliest 'Monitor for governance events starting at the blockchain's first block' + [latest] --latest 'Monitor for governance events starting at the blockchain's most recently mined block' + [start_block] --start [value] 'Start monitoring for governance events at this block (inclusive)' + [tail] --tail [value] 'Start monitoring for governance events for the `n` blocks prior to the last mined block' + [email] --email 'Enables email notifications (SMTP configuration options must be set in your `.env` file)' + [block_time] --block-time [value] 'The average number of seconds it takes to mine a new block' + [notification_limit] -n --limit [value] 'Stops `poagov` after this many notifications have been generated (this option can be useful when testing `poagov`)' + [log_emails] --log-emails 'Logs the full email body for each notification generated, this option does not require the `--email` flag to be set' + [log_to_file] --log-file 'Logs are written to files in the ./logs directory, logs are rotated chronologically across 3 files, each file has a max size of 8MB'" ).get_matches(); Cli(cli_args) @@ -42,6 +43,19 @@ impl Cli { self.0.is_present("sokol") } + pub fn xdai(&self) -> bool { + self.0.is_present("xdai") + } + + pub fn one_network_specified(&self) -> bool { + match (self.core(), self.sokol(), self.xdai()) { + (true, false, false) => true, + (false, true, false) => true, + (false, false, true) => true, + _ => false, + } + } + pub fn keys(&self) -> bool { self.0.is_present("keys") } @@ -70,6 +84,10 @@ impl Cli { self.0.is_present("v2") } + pub fn multiple_versions_specified(&self) -> bool { + self.v1() && self.v2() + } + pub fn earliest(&self) -> bool { self.0.is_present("earliest") } @@ -86,21 +104,14 @@ impl Cli { self.0.value_of("tail") } - pub fn multiple_start_blocks_specified(&self) -> bool { - let mut count = 0; - if self.earliest() { - count += 1; + pub fn one_start_block_was_specified(&self) -> bool { + match (self.earliest(), self.latest(), self.start_block().is_some(), self.tail().is_some()) { + (true, false, false, false) => true, + (false, true, false, false) => true, + (false, false, true, false) => true, + (false, false, false, true) => true, + _ => false, } - if self.latest() { - count += 1; - } - if self.start_block().is_some() { - count += 1; - } - if self.tail().is_some() { - count += 1; - } - count != 1 } pub fn email(&self) -> bool { diff --git a/src/config.rs b/src/config.rs index ab61cfb..3c5e62e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,6 +15,7 @@ const DEFAULT_BLOCK_TIME_SECS: u64 = 30; pub enum Network { Core, Sokol, + XDai, } impl Network { @@ -22,6 +23,7 @@ impl Network { match self { Network::Core => "CORE", Network::Sokol => "SOKOL", + Network::XDai=> "XDAI", } } } @@ -190,18 +192,26 @@ pub struct Config { impl Config { pub fn new(cli: &Cli) -> Result { - if cli.core() == cli.sokol() { + if !cli.one_network_specified() { return Err(Error::MustSpecifyOneCliArgument("--core, --sokol".to_string())); } - if cli.v1() == cli.v2() { - return Err(Error::MustSpecifyOneCliArgument("--v1, --v2".to_string())); + if cli.multiple_versions_specified() { + return Err(Error::MustSpecifyZeroOrOneCliArguments("--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() { + if cli.v1() { + if cli.xdai() { + return Err(Error::V1ContractsWereNotDeployedToXDaiChain); + } + if cli.emission() { + return Err(Error::EmissionFundsV1ContractDoesNotExist); + } + } + if !cli.one_start_block_was_specified() { return Err(Error::MustSpecifyOneCliArgument( "--earliest, --latest, --start-block, --tail".to_string() )); @@ -209,10 +219,13 @@ impl Config { let network = if cli.core() { Network::Core - } else { + } else if cli.sokol() { Network::Sokol + } else { + Network::XDai }; + // We default to `V2` if no contract version CLI argument was supplied. let version = if cli.v1() { ContractVersion::V1 } else { @@ -268,23 +281,26 @@ impl Config { let email_notifications = cli.email(); + // TODO: should the recipient email addresses be validated here? For now, we just allow + // email sending to fail, which will then get logged to the user. let email_recipients: Vec = env::var("EMAIL_RECIPIENTS") - .map_err(|_| Error::MissingEnvVar("EMAIL_RECIPIENTS".into()))? + .map_err(|_| Error::MissingEnvVar("EMAIL_RECIPIENTS".to_string()))? .split(',') - .filter_map(|s| if s.is_empty() { None } else { Some(s.into()) }) + .map(|recipient_email_address| recipient_email_address.to_string()) .collect(); let smtp_host_domain = if email_notifications { - let host = env::var("SMTP_HOST_DOMAIN") - .map_err(|_| Error::MissingEnvVar("SMTP_HOST_DOMAIN".into()))?; - Some(host) + match env::var("SMTP_HOST_DOMAIN") { + Ok(host) => Some(host), + _ => return Err(Error::MissingEnvVar("SMTP_HOST_DOMAIN".to_string())), + } } else { None }; let smtp_port = if email_notifications { if let Ok(s) = env::var("SMTP_PORT") { - let port = s.parse().map_err(|_| Error::InvalidSmtpPort(s.into()))?; + let port = s.parse().map_err(|_| Error::InvalidSmtpPort(s.to_string()))?; Some(port) } else { return Err(Error::MissingEnvVar("SMTP_PORT".into())); @@ -294,25 +310,28 @@ impl Config { }; let smtp_username = if email_notifications { - let username = env::var("SMTP_USERNAME") - .map_err(|_| Error::MissingEnvVar("SMTP_USERNAME".into()))?; - Some(username) + match env::var("SMTP_USERNAME") { + Ok(username) => Some(username), + _ => return Err(Error::MissingEnvVar("SMTP_USERNAME".into())), + } } else { None }; let smtp_password = if email_notifications { - let password = env::var("SMTP_PASSWORD") - .map_err(|_| Error::MissingEnvVar("SMTP_PASSWORD".into()))?; - Some(password) + match env::var("SMTP_PASSWORD") { + Ok(password) => Some(password), + _ => return Err(Error::MissingEnvVar("SMTP_PASSWORD".to_string())), + } } else { None }; let outgoing_email_addr = if email_notifications { - let email_addr = env::var("OUTGOING_EMAIL_ADDRESS") - .map_err(|_| Error::MissingEnvVar("OUTGOING_EMAIL_ADDRESS".into()))?; - Some(email_addr) + match env::var("OUTGOING_EMAIL_ADDRESS") { + Ok(outgoing_email_addr) => Some(outgoing_email_addr), + _ => return Err(Error::MissingEnvVar("OUTGOING_EMAIL_ADDRESS".to_string())), + } } else { None }; diff --git a/src/error.rs b/src/error.rs index 5a1eac6..8e436ff 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,9 +23,11 @@ pub enum Error { MissingEnvVar(String), MustSpecifyAtLeastOneCliArgument(String), MustSpecifyOneCliArgument(String), + MustSpecifyZeroOrOneCliArguments(String), RequestFailed(reqwest::Error), StartBlockExceedsLastBlockMined { start_block: u64, last_mined_block: u64, }, + V1ContractsWereNotDeployedToXDaiChain, }