2018-03-31 07:57:49 -07:00
|
|
|
extern crate clap;
|
|
|
|
extern crate colored;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate error_chain;
|
|
|
|
extern crate ethabi;
|
2018-05-09 08:11:25 -07:00
|
|
|
#[macro_use]
|
|
|
|
extern crate ethabi_derive;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate ethabi_contract;
|
2018-03-31 11:31:53 -07:00
|
|
|
extern crate serde;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate serde_derive;
|
|
|
|
extern crate serde_json;
|
2018-03-31 07:57:49 -07:00
|
|
|
extern crate web3;
|
|
|
|
|
|
|
|
mod cli;
|
|
|
|
mod error;
|
|
|
|
mod events;
|
|
|
|
mod stats;
|
|
|
|
mod util;
|
|
|
|
mod validator;
|
|
|
|
|
|
|
|
use error::{Error, ErrorKind};
|
2018-05-09 08:11:25 -07:00
|
|
|
use events::{BallotCreated, Vote};
|
2018-03-31 07:57:49 -07:00
|
|
|
use stats::Stats;
|
2018-03-31 11:31:53 -07:00
|
|
|
use std::default::Default;
|
2018-03-31 07:57:49 -07:00
|
|
|
use std::fs::File;
|
2018-05-09 08:11:25 -07:00
|
|
|
use util::{ContractExt, HexBytes, HexList, TopicFilterExt, Web3LogExt};
|
2018-03-31 07:57:49 -07:00
|
|
|
use web3::futures::Future;
|
|
|
|
|
2018-05-09 08:11:25 -07:00
|
|
|
use_contract!(
|
|
|
|
net_con,
|
|
|
|
"NetworkConsensus",
|
|
|
|
"abi/PoaNetworkConsensus.abi.json"
|
|
|
|
);
|
|
|
|
// use_contract!(
|
|
|
|
// voting,
|
|
|
|
// "VotingToChangeKeys",
|
|
|
|
// "abi/VotingToChangeKeys.abi.json"
|
|
|
|
// );
|
2018-03-31 07:57:49 -07:00
|
|
|
|
2018-03-31 11:31:53 -07:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
|
|
struct ContractAddresses {
|
|
|
|
metadata_address: String,
|
|
|
|
keys_manager_address: String,
|
|
|
|
}
|
|
|
|
|
2018-03-31 07:57:49 -07:00
|
|
|
/// Finds all logged ballots and returns statistics about how many were missed by each voter.
|
|
|
|
fn count_votes(
|
|
|
|
url: &str,
|
|
|
|
verbose: bool,
|
2018-03-31 11:35:57 -07:00
|
|
|
contract_addrs: &ContractAddresses,
|
2018-03-31 07:57:49 -07:00
|
|
|
) -> Result<Stats, Error> {
|
|
|
|
let (_eloop, transport) = web3::transports::Http::new(url).unwrap();
|
|
|
|
let web3 = web3::Web3::new(transport);
|
2018-03-31 11:31:53 -07:00
|
|
|
|
|
|
|
let voting_abi = File::open("abi/VotingToChangeKeys.abi.json").expect("read voting abi");
|
|
|
|
let val_meta_abi = File::open("abi/ValidatorMetadata.abi.json").expect("read val meta abi");
|
|
|
|
let key_mgr_abi = File::open("abi/KeysManager.abi.json").expect("read key mgr abi");
|
|
|
|
|
2018-03-31 07:57:49 -07:00
|
|
|
let voting_contract = ethabi::Contract::load(voting_abi)?;
|
2018-05-09 08:11:25 -07:00
|
|
|
let net_con_contract = net_con::NetworkConsensus::default();
|
2018-03-31 07:57:49 -07:00
|
|
|
let val_meta_contract = ethabi::Contract::load(val_meta_abi)?;
|
2018-03-31 10:37:09 -07:00
|
|
|
let key_mgr_contract = ethabi::Contract::load(key_mgr_abi)?;
|
2018-03-31 07:57:49 -07:00
|
|
|
|
2018-03-31 11:31:53 -07:00
|
|
|
let val_meta_addr = util::parse_address(&contract_addrs.metadata_address).unwrap();
|
2018-03-31 07:57:49 -07:00
|
|
|
let web3_val_meta = web3::contract::Contract::new(web3.eth(), val_meta_addr, val_meta_contract);
|
2018-03-31 11:31:53 -07:00
|
|
|
let key_mgr_addr = util::parse_address(&contract_addrs.keys_manager_address).unwrap();
|
2018-03-31 10:37:09 -07:00
|
|
|
let web3_key_mgr = web3::contract::Contract::new(web3.eth(), key_mgr_addr, key_mgr_contract);
|
2018-03-31 07:57:49 -07:00
|
|
|
|
|
|
|
let ballot_event = voting_contract.event("BallotCreated")?;
|
|
|
|
let vote_event = voting_contract.event("Vote")?;
|
2018-05-09 08:11:25 -07:00
|
|
|
let change_event = net_con_contract.events().change_finalized();
|
|
|
|
let init_change_event = net_con_contract.events().initiate_change();
|
2018-03-31 07:57:49 -07:00
|
|
|
|
|
|
|
// Find all ballots and voter changes.
|
2018-05-09 08:11:25 -07:00
|
|
|
let ballot_or_change_filter = ballot_event
|
|
|
|
.create_filter(ethabi::RawTopicFilter::default())
|
|
|
|
.expect("create ballot event filter")
|
|
|
|
.or(change_event.create_filter())
|
|
|
|
.or(init_change_event.create_filter(ethabi::Topic::Any))
|
|
|
|
.to_filter_builder()
|
2018-03-31 07:57:49 -07:00
|
|
|
.build();
|
|
|
|
let ballot_change_logs_filter = web3.eth_filter()
|
|
|
|
.create_logs_filter(ballot_or_change_filter)
|
|
|
|
.wait()?;
|
|
|
|
|
|
|
|
// FIXME: Find out why we see no `ChangeFinalized` events, and how to obtain the initial voters.
|
|
|
|
let mut voters: Vec<ethabi::Address> = Vec::new();
|
|
|
|
let mut stats = Stats::default();
|
2018-05-09 08:11:25 -07:00
|
|
|
let mut prev_init_change: Option<net_con::logs::InitiateChange> = None;
|
2018-03-31 07:57:49 -07:00
|
|
|
|
2018-05-07 02:22:51 -07:00
|
|
|
if verbose {
|
|
|
|
println!("Collecting events…");
|
|
|
|
}
|
|
|
|
let mut event_found = false;
|
|
|
|
|
2018-03-31 07:57:49 -07:00
|
|
|
// Iterate over all ballot and voter change events.
|
|
|
|
for log in ballot_change_logs_filter.logs().wait()? {
|
2018-05-07 02:22:51 -07:00
|
|
|
event_found = true;
|
2018-05-09 08:11:25 -07:00
|
|
|
if let Ok(change) = change_event.parse_log(log.clone().into_raw()) {
|
2018-03-31 07:57:49 -07:00
|
|
|
// If it is a `ChangeFinalized`, update the current set of voters.
|
|
|
|
if verbose {
|
2018-05-09 08:11:25 -07:00
|
|
|
println!(
|
|
|
|
"• ChangeFinalized {{ new_set: {} }}",
|
|
|
|
HexList(&change.new_set)
|
|
|
|
);
|
2018-03-31 07:57:49 -07:00
|
|
|
}
|
|
|
|
voters = change.new_set;
|
2018-05-09 08:11:25 -07:00
|
|
|
} else if let Ok(init_change) = init_change_event.parse_log(log.clone().into_raw()) {
|
2018-03-31 10:37:09 -07:00
|
|
|
// If it is an `InitiateChange`, update the current set of voters.
|
|
|
|
if verbose {
|
2018-05-09 08:11:25 -07:00
|
|
|
println!(
|
|
|
|
"• InitiateChange {{ parent_hash: {}, new_set: {} }}",
|
|
|
|
HexBytes(&init_change.parent_hash),
|
|
|
|
HexList(&init_change.new_set)
|
|
|
|
);
|
2018-03-31 10:37:09 -07:00
|
|
|
}
|
|
|
|
if let Some(prev) = prev_init_change.take() {
|
|
|
|
voters = vec![];
|
|
|
|
for mining_key in prev.new_set {
|
|
|
|
let voter = web3_key_mgr.simple_query("getVotingByMining", mining_key)?;
|
|
|
|
if voter != ethabi::Address::zero() {
|
|
|
|
voters.push(voter);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
prev_init_change = Some(init_change);
|
2018-03-31 07:57:49 -07:00
|
|
|
} else if let Ok(ballot_log) = ballot_event.parse_log(log.into_raw()) {
|
|
|
|
// If it is a `BallotCreated`, find the corresponding votes and update the stats.
|
|
|
|
let ballot = BallotCreated::from_log(&ballot_log)?;
|
|
|
|
if verbose {
|
2018-05-07 02:22:51 -07:00
|
|
|
println!("• {}", ballot);
|
2018-03-31 07:57:49 -07:00
|
|
|
}
|
|
|
|
let vote_filter = vote_event
|
|
|
|
.create_filter(ballot.vote_topic_filter())?
|
|
|
|
.to_filter_builder()
|
|
|
|
.build();
|
|
|
|
let vote_logs_filter = web3.eth_filter().create_logs_filter(vote_filter).wait()?;
|
2018-05-07 02:30:36 -07:00
|
|
|
let vote_logs = vote_logs_filter.logs().wait()?;
|
|
|
|
let votes = vote_logs
|
2018-05-07 02:22:51 -07:00
|
|
|
.into_iter()
|
|
|
|
.map(|vote_log| {
|
|
|
|
let vote = Vote::from_log(&vote_event.parse_log(vote_log.into_raw())?)?;
|
|
|
|
if !voters.contains(&vote.voter) {
|
|
|
|
if verbose {
|
|
|
|
eprintln!(" Unexpected voter {}", vote.voter);
|
|
|
|
}
|
|
|
|
voters.push(vote.voter);
|
2018-03-31 11:35:57 -07:00
|
|
|
}
|
2018-05-07 02:22:51 -07:00
|
|
|
Ok(vote)
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<Vote>, Error>>()?;
|
2018-03-31 07:57:49 -07:00
|
|
|
stats.add_ballot(&voters, &votes);
|
|
|
|
} else {
|
|
|
|
return Err(ErrorKind::UnexpectedLogParams.into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-07 02:22:51 -07:00
|
|
|
if !event_found {
|
|
|
|
return Err(ErrorKind::NoEventsFound.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
if verbose {
|
|
|
|
println!(""); // Add a new line between event log and table.
|
|
|
|
}
|
|
|
|
|
2018-03-31 07:57:49 -07:00
|
|
|
// Finally, gather the metadata for all voters.
|
|
|
|
for voter in voters {
|
2018-05-07 02:22:51 -07:00
|
|
|
let mining_key = match web3_val_meta.simple_query("getMiningByVotingKey", voter) {
|
|
|
|
Ok(key) => key,
|
|
|
|
Err(err) => {
|
|
|
|
eprintln!("Could not fetch mining key for {:?}: {:?}", voter, err);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
};
|
2018-03-31 07:57:49 -07:00
|
|
|
let validator = web3_val_meta.simple_query("validators", mining_key)?;
|
|
|
|
stats.set_metadata(&voter, mining_key, validator);
|
|
|
|
}
|
|
|
|
Ok(stats)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let matches = cli::get_matches();
|
|
|
|
let url = matches.value_of("url").unwrap_or("http://127.0.0.1:8545");
|
|
|
|
let verbose = matches.is_present("verbose");
|
2018-05-07 02:22:51 -07:00
|
|
|
let contract_file = matches
|
2018-03-31 11:31:53 -07:00
|
|
|
.value_of("contracts")
|
2018-05-07 02:22:51 -07:00
|
|
|
.unwrap_or("contracts/core.json");
|
|
|
|
let file = File::open(contract_file).expect("open contracts file");
|
|
|
|
let contract_addrs = serde_json::from_reader(file).expect("parse contracts file");
|
2018-03-31 11:35:57 -07:00
|
|
|
let stats = count_votes(url, verbose, &contract_addrs).expect("count votes");
|
2018-03-31 07:57:49 -07:00
|
|
|
println!("{}", stats);
|
|
|
|
}
|