Use ethabi-contract for all contracts.
This commit is contained in:
parent
994d0ae202
commit
cc46cc7066
105
src/main.rs
105
src/main.rs
|
@ -20,11 +20,11 @@ mod util;
|
|||
mod validator;
|
||||
|
||||
use error::{Error, ErrorKind};
|
||||
use ethabi::Address;
|
||||
use stats::Stats;
|
||||
use std::default::Default;
|
||||
use std::fs::File;
|
||||
use util::{ContractExt, HexBytes, HexList, TopicFilterExt, Web3LogExt};
|
||||
use web3::futures::Future;
|
||||
use util::{HexBytes, HexList, TopicFilterExt, Web3LogExt};
|
||||
|
||||
use_contract!(
|
||||
net_con,
|
||||
|
@ -36,6 +36,12 @@ use_contract!(
|
|||
"VotingToChangeKeys",
|
||||
"abi/VotingToChangeKeys.abi.json"
|
||||
);
|
||||
use_contract!(
|
||||
val_meta,
|
||||
"ValidatorMetadata",
|
||||
"abi/ValidatorMetadata.abi.json"
|
||||
);
|
||||
use_contract!(key_mgr, "KeysManager", "abi/KeysManager.abi.json");
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
|
@ -50,21 +56,19 @@ fn count_votes(
|
|||
verbose: bool,
|
||||
contract_addrs: &ContractAddresses,
|
||||
) -> Result<Stats, Error> {
|
||||
// Calls `println!` if `verbose` is `true`.
|
||||
macro_rules! vprintln { ($($arg:tt)*) => { if verbose { println!($($arg)*); } } }
|
||||
|
||||
let (_eloop, transport) = web3::transports::Http::new(url).unwrap();
|
||||
let web3 = web3::Web3::new(transport);
|
||||
|
||||
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");
|
||||
|
||||
let voting_contract = voting::VotingToChangeKeys::default();
|
||||
let net_con_contract = net_con::NetworkConsensus::default();
|
||||
let val_meta_contract = ethabi::Contract::load(val_meta_abi)?;
|
||||
let key_mgr_contract = ethabi::Contract::load(key_mgr_abi)?;
|
||||
let val_meta_contract = val_meta::ValidatorMetadata::default();
|
||||
let key_mgr_contract = key_mgr::KeysManager::default();
|
||||
|
||||
let val_meta_addr = util::parse_address(&contract_addrs.metadata_address).unwrap();
|
||||
let web3_val_meta = web3::contract::Contract::new(web3.eth(), val_meta_addr, val_meta_contract);
|
||||
let key_mgr_addr = util::parse_address(&contract_addrs.keys_manager_address).unwrap();
|
||||
let web3_key_mgr = web3::contract::Contract::new(web3.eth(), key_mgr_addr, key_mgr_contract);
|
||||
|
||||
let ballot_event = voting_contract.events().ballot_created();
|
||||
let vote_event = voting_contract.events().vote();
|
||||
|
@ -72,52 +76,42 @@ fn count_votes(
|
|||
let init_change_event = net_con_contract.events().initiate_change();
|
||||
|
||||
// Find all ballots and voter changes.
|
||||
let ballot_or_change_filter =
|
||||
(ballot_event.create_filter(ethabi::Topic::Any, ethabi::Topic::Any, ethabi::Topic::Any))
|
||||
.or(change_event.create_filter())
|
||||
.or(init_change_event.create_filter(ethabi::Topic::Any))
|
||||
.to_filter_builder()
|
||||
.build();
|
||||
let ballot_change_logs_filter = web3.eth_filter()
|
||||
.create_logs_filter(ballot_or_change_filter)
|
||||
.wait()?;
|
||||
let ballot_or_change_filter = (ballot_event.create_filter(None, None, None))
|
||||
.or(change_event.create_filter())
|
||||
.or(init_change_event.create_filter(None));
|
||||
|
||||
// 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 voters: Vec<Address> = Vec::new();
|
||||
let mut stats = Stats::default();
|
||||
let mut prev_init_change: Option<net_con::logs::InitiateChange> = None;
|
||||
|
||||
if verbose {
|
||||
println!("Collecting events…");
|
||||
}
|
||||
vprintln!("Collecting events…");
|
||||
let mut event_found = false;
|
||||
|
||||
// Iterate over all ballot and voter change events.
|
||||
for log in ballot_change_logs_filter.logs().wait()? {
|
||||
for log in ballot_or_change_filter.logs(&web3)? {
|
||||
event_found = true;
|
||||
if let Ok(change) = change_event.parse_log(log.clone().into_raw()) {
|
||||
// If it is a `ChangeFinalized`, update the current set of voters.
|
||||
if verbose {
|
||||
println!(
|
||||
"• ChangeFinalized {{ new_set: {} }}",
|
||||
HexList(&change.new_set)
|
||||
);
|
||||
}
|
||||
vprintln!(
|
||||
"• ChangeFinalized {{ new_set: {} }}",
|
||||
HexList(&change.new_set)
|
||||
);
|
||||
voters = change.new_set;
|
||||
} else if let Ok(init_change) = init_change_event.parse_log(log.clone().into_raw()) {
|
||||
// If it is an `InitiateChange`, update the current set of voters.
|
||||
if verbose {
|
||||
println!(
|
||||
"• InitiateChange {{ parent_hash: {}, new_set: {} }}",
|
||||
HexBytes(&init_change.parent_hash),
|
||||
HexList(&init_change.new_set)
|
||||
);
|
||||
}
|
||||
vprintln!(
|
||||
"• InitiateChange {{ parent_hash: {}, new_set: {} }}",
|
||||
HexBytes(&init_change.parent_hash),
|
||||
HexList(&init_change.new_set)
|
||||
);
|
||||
if let Some(prev) = prev_init_change.take() {
|
||||
let raw_call = util::raw_call(key_mgr_addr, web3.eth());
|
||||
let get_voting_by_mining_fn = key_mgr_contract.functions().get_voting_by_mining();
|
||||
voters = vec![];
|
||||
for mining_key in prev.new_set {
|
||||
let voter = web3_key_mgr.simple_query("getVotingByMining", mining_key)?;
|
||||
if voter != ethabi::Address::zero() {
|
||||
let voter = get_voting_by_mining_fn.call(mining_key, &*raw_call)?;
|
||||
if voter != Address::zero() {
|
||||
voters.push(voter);
|
||||
}
|
||||
}
|
||||
|
@ -125,23 +119,15 @@ fn count_votes(
|
|||
prev_init_change = Some(init_change);
|
||||
} else if let Ok(ballot) = ballot_event.parse_log(log.into_raw()) {
|
||||
// If it is a `BallotCreated`, find the corresponding votes and update the stats.
|
||||
if verbose {
|
||||
println!("• {:?}", ballot);
|
||||
}
|
||||
let vote_filter = vote_event
|
||||
.create_filter(ballot.id, ethabi::Topic::Any)
|
||||
.to_filter_builder()
|
||||
.build();
|
||||
let vote_logs_filter = web3.eth_filter().create_logs_filter(vote_filter).wait()?;
|
||||
let vote_logs = vote_logs_filter.logs().wait()?;
|
||||
let votes = vote_logs
|
||||
vprintln!("• {:?}", ballot);
|
||||
let votes = vote_event
|
||||
.create_filter(ballot.id, None)
|
||||
.logs(&web3)?
|
||||
.into_iter()
|
||||
.map(|vote_log| {
|
||||
let vote = vote_event.parse_log(vote_log.into_raw())?;
|
||||
if !voters.contains(&vote.voter) {
|
||||
if verbose {
|
||||
eprintln!(" Unexpected voter {}", vote.voter);
|
||||
}
|
||||
vprintln!(" Unexpected voter {}", vote.voter);
|
||||
voters.push(vote.voter);
|
||||
}
|
||||
Ok(vote)
|
||||
|
@ -157,20 +143,15 @@ fn count_votes(
|
|||
return Err(ErrorKind::NoEventsFound.into());
|
||||
}
|
||||
|
||||
if verbose {
|
||||
println!(""); // Add a new line between event log and table.
|
||||
}
|
||||
vprintln!(""); // Add a new line between event log and table.
|
||||
|
||||
// Finally, gather the metadata for all voters.
|
||||
let raw_call = util::raw_call(val_meta_addr, web3.eth());
|
||||
let get_mining_by_voting_key_fn = val_meta_contract.functions().get_mining_by_voting_key();
|
||||
let validators_fn = val_meta_contract.functions().validators();
|
||||
for voter in voters {
|
||||
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;
|
||||
}
|
||||
};
|
||||
let validator = web3_val_meta.simple_query("validators", mining_key)?;
|
||||
let mining_key = get_mining_by_voting_key_fn.call(voter, &*raw_call)?;
|
||||
let validator = validators_fn.call(mining_key, &*raw_call)?.into();
|
||||
stats.set_metadata(&voter, mining_key, validator);
|
||||
}
|
||||
Ok(stats)
|
||||
|
|
81
src/util.rs
81
src/util.rs
|
@ -1,21 +1,12 @@
|
|||
use ethabi;
|
||||
use ethabi::{self, Address, Bytes, Uint};
|
||||
use std::{fmt, u8};
|
||||
use web3;
|
||||
use web3::futures::Future;
|
||||
|
||||
// TODO: Evaluate whether any of these would make sense to include in `web3`.
|
||||
|
||||
/// Converts the bytes to a string, interpreting them as null-terminated UTF-8.
|
||||
pub fn bytes_to_string(bytes: &[u8]) -> String {
|
||||
let zero = bytes
|
||||
.iter()
|
||||
.position(|b| *b == 0)
|
||||
.unwrap_or_else(|| bytes.len());
|
||||
String::from_utf8_lossy(&bytes[..zero]).to_string()
|
||||
}
|
||||
|
||||
/// Parses the string as a 40-digit hexadecimal number, and returns the corresponding `Address`.
|
||||
pub fn parse_address(mut s: &str) -> Option<ethabi::Address> {
|
||||
pub fn parse_address(mut s: &str) -> Option<Address> {
|
||||
let mut bytes = [0u8; 20];
|
||||
if &s[..2] == "0x" {
|
||||
s = &s[2..];
|
||||
|
@ -26,31 +17,28 @@ pub fn parse_address(mut s: &str) -> Option<ethabi::Address> {
|
|||
Err(_) => return None,
|
||||
}
|
||||
}
|
||||
Some(ethabi::Address::from_slice(&bytes))
|
||||
Some(Address::from_slice(&bytes))
|
||||
}
|
||||
|
||||
pub trait ContractExt {
|
||||
fn simple_query<P, R>(&self, func: &str, params: P) -> Result<R, web3::contract::Error>
|
||||
where
|
||||
R: web3::contract::tokens::Detokenize,
|
||||
P: web3::contract::tokens::Tokenize;
|
||||
}
|
||||
|
||||
impl ContractExt for web3::contract::Contract<web3::transports::Http> {
|
||||
/// Calls a constant function with the latest block and default parameters.
|
||||
fn simple_query<P, R>(&self, func: &str, params: P) -> Result<R, web3::contract::Error>
|
||||
where
|
||||
R: web3::contract::tokens::Detokenize,
|
||||
P: web3::contract::tokens::Tokenize,
|
||||
{
|
||||
self.query(
|
||||
func,
|
||||
params,
|
||||
None,
|
||||
web3::contract::Options::default(),
|
||||
web3::types::BlockNumber::Latest,
|
||||
).wait()
|
||||
}
|
||||
/// Returns a wrapper of a contract address, to make function calls using the latest block.
|
||||
pub fn raw_call<T: web3::Transport + 'static>(
|
||||
to: Address,
|
||||
eth: web3::api::Eth<T>,
|
||||
) -> Box<Fn(Bytes) -> Result<Bytes, String>> {
|
||||
Box::new(move |bytes: Bytes| -> Result<Bytes, String> {
|
||||
let req = web3::types::CallRequest {
|
||||
from: None,
|
||||
to,
|
||||
gas: None,
|
||||
gas_price: None,
|
||||
value: None,
|
||||
data: Some(bytes.into()),
|
||||
};
|
||||
eth.call(req, Some(web3::types::BlockNumber::Latest))
|
||||
.wait()
|
||||
.map(|bytes| bytes.0)
|
||||
.map_err(|err| err.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
trait TopicExt<T> {
|
||||
|
@ -97,6 +85,12 @@ pub trait TopicFilterExt {
|
|||
/// Returns the "disjunction" of the two filters, i.e. it filters for everything that matches
|
||||
/// at least one of the two in every topic.
|
||||
fn or(self, other: ethabi::TopicFilter) -> ethabi::TopicFilter;
|
||||
|
||||
/// Returns the vector of logs that match this filter.
|
||||
fn logs<T: web3::Transport>(
|
||||
self,
|
||||
web3: &web3::Web3<T>,
|
||||
) -> Result<Vec<web3::types::Log>, web3::error::Error>;
|
||||
}
|
||||
|
||||
impl TopicFilterExt for ethabi::TopicFilter {
|
||||
|
@ -120,6 +114,17 @@ impl TopicFilterExt for ethabi::TopicFilter {
|
|||
topic3: self.topic3.or(other.topic3),
|
||||
}
|
||||
}
|
||||
|
||||
fn logs<T: web3::Transport>(
|
||||
self,
|
||||
web3: &web3::Web3<T>,
|
||||
) -> Result<Vec<web3::types::Log>, web3::error::Error> {
|
||||
web3.eth_filter()
|
||||
.create_logs_filter(self.to_filter_builder().build())
|
||||
.wait()?
|
||||
.logs()
|
||||
.wait()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Web3LogExt {
|
||||
|
@ -138,10 +143,10 @@ pub trait LogExt {
|
|||
|
||||
/// Returns the `i`-th parameter, if it is an `Address` and has the given name, otherwise
|
||||
/// `None`.
|
||||
fn address_param(&self, i: usize, name: &str) -> Option<ðabi::Address>;
|
||||
fn address_param(&self, i: usize, name: &str) -> Option<&Address>;
|
||||
|
||||
/// Returns the `i`-th parameter, if it is a `Uint` and has the given name, otherwise `None`.
|
||||
fn uint_param(&self, i: usize, name: &str) -> Option<ðabi::Uint>;
|
||||
fn uint_param(&self, i: usize, name: &str) -> Option<&Uint>;
|
||||
}
|
||||
|
||||
impl LogExt for ethabi::Log {
|
||||
|
@ -155,14 +160,14 @@ impl LogExt for ethabi::Log {
|
|||
})
|
||||
}
|
||||
|
||||
fn address_param(&self, i: usize, name: &str) -> Option<ðabi::Address> {
|
||||
fn address_param(&self, i: usize, name: &str) -> Option<&Address> {
|
||||
match self.param(i, name) {
|
||||
Some(ðabi::Token::Address(ref address)) => Some(address),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn uint_param(&self, i: usize, name: &str) -> Option<ðabi::Uint> {
|
||||
fn uint_param(&self, i: usize, name: &str) -> Option<&Uint> {
|
||||
match self.param(i, name) {
|
||||
Some(ðabi::Token::Uint(ref i)) => Some(i),
|
||||
_ => None,
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use ethabi::Token;
|
||||
use util;
|
||||
use web3::contract::{tokens, Error, ErrorKind};
|
||||
use ethabi;
|
||||
|
||||
/// Validator metadata.
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -17,17 +15,28 @@ pub struct Validator {
|
|||
// uint256 minThreshold,
|
||||
}
|
||||
|
||||
impl tokens::Detokenize for Validator {
|
||||
/// Returns a `Validator` if the token's types match the fields.
|
||||
fn from_tokens(tokens: Vec<Token>) -> Result<Validator, Error> {
|
||||
match (tokens.get(0), tokens.get(1)) {
|
||||
(Some(&Token::FixedBytes(ref first)), Some(&Token::FixedBytes(ref last))) => {
|
||||
Ok(Validator {
|
||||
first_name: util::bytes_to_string(first),
|
||||
last_name: util::bytes_to_string(last),
|
||||
})
|
||||
}
|
||||
_ => Err(ErrorKind::InvalidOutputType("Validator".to_string()).into()),
|
||||
type ValidatorTuple = (
|
||||
ethabi::Hash,
|
||||
ethabi::Hash,
|
||||
ethabi::Hash,
|
||||
String,
|
||||
ethabi::Hash,
|
||||
ethabi::Hash,
|
||||
ethabi::Uint,
|
||||
ethabi::Uint,
|
||||
ethabi::Uint,
|
||||
ethabi::Uint,
|
||||
);
|
||||
|
||||
impl From<ValidatorTuple> for Validator {
|
||||
fn from((first_name_h, last_name_h, ..): ValidatorTuple) -> Validator {
|
||||
Validator {
|
||||
first_name: String::from_utf8_lossy(&*first_name_h)
|
||||
.to_owned()
|
||||
.to_string(),
|
||||
last_name: String::from_utf8_lossy(&*last_name_h)
|
||||
.to_owned()
|
||||
.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue