Use ethabi-contract for all contracts.

This commit is contained in:
Andreas Fackler 2018-05-10 10:44:07 +02:00
parent 994d0ae202
commit cc46cc7066
3 changed files with 109 additions and 114 deletions

View File

@ -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)

View File

@ -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<&ethabi::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<&ethabi::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<&ethabi::Address> {
fn address_param(&self, i: usize, name: &str) -> Option<&Address> {
match self.param(i, name) {
Some(&ethabi::Token::Address(ref address)) => Some(address),
_ => None,
}
}
fn uint_param(&self, i: usize, name: &str) -> Option<&ethabi::Uint> {
fn uint_param(&self, i: usize, name: &str) -> Option<&Uint> {
match self.param(i, name) {
Some(&ethabi::Token::Uint(ref i)) => Some(i),
_ => None,

View File

@ -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(),
}
}
}