Use ethabi-contract.

This commit is contained in:
Andreas Fackler 2018-05-09 17:11:25 +02:00
parent 85bb8d26e3
commit 78f2da0a4a
5 changed files with 150 additions and 119 deletions

35
Cargo.lock generated
View File

@ -206,6 +206,22 @@ dependencies = [
"tiny-keccak 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ethabi-contract"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ethabi-derive"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ethabi 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ethbloom"
version = "0.5.0"
@ -300,6 +316,14 @@ dependencies = [
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "heck"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "httparse"
version = "1.2.4"
@ -619,6 +643,8 @@ dependencies = [
"colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethabi 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ethabi-contract 5.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethabi-derive 5.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1120,6 +1146,11 @@ name = "unicode-normalization"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-segmentation"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.4"
@ -1267,6 +1298,8 @@ dependencies = [
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
"checksum ethabi 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05e33a914b94b763f0a92333e4e5c95c095563f06ef7d6b295b3d3c2cf31e21f"
"checksum ethabi-contract 5.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "210c9e21d164c15b6ef64fe601e0e12a3c84a031d5ef558e38463e53edbd22ed"
"checksum ethabi-derive 5.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d2bc7099baa147187aedaecd9fe04a6c0541c82bc43ff317cb6900fe2b983d74"
"checksum ethbloom 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a93a43ce2e9f09071449da36bfa7a1b20b950ee344b6904ff23de493b03b386"
"checksum ethereum-types 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3ae691a36ce5d25b433e63128ce5579f4a18457b6a9c849832b2c9e0fec92a"
"checksum ethereum-types-serialize 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ac59a21a9ce98e188f3dace9eb67a6c4a3c67ec7fbc7218cb827852679dc002"
@ -1278,6 +1311,7 @@ dependencies = [
"checksum futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "1a70b146671de62ec8c8ed572219ca5d594d9b06c0b364d5e67b722fc559b48c"
"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
"checksum heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461"
"checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82"
"checksum httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2f407128745b78abc95c0ffbe4e5d37427fdc0d45470710cfef8c44522a2e37"
"checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2"
"checksum hyper 0.11.26 (registry+https://github.com/rust-lang/crates.io-index)" = "66b16eb6213713f3c72d0ed14ce56423ae84dced8df73d2a2c8675f0495ae7ea"
@ -1370,6 +1404,7 @@ dependencies = [
"checksum unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284b6d3db520d67fbe88fd778c21510d1b0ba4a551e5d0fbb023d33405f6de8a"
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
"checksum unicode-normalization 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "90d662d111b0dbb08a180f2761026cba648c258023c355954a7c00e00e354636"
"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1"
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7"

View File

@ -9,6 +9,8 @@ clap = "2.31.2"
colored = "1.6.0"
error-chain = { version = "0.11", default-features = false }
ethabi = "5.1.1"
ethabi-contract = "5.1.0"
ethabi-derive = "5.1.2"
serde = "1.0.36"
serde_derive = "1.0.36"
serde_json = "1.0.13"

View File

@ -1,83 +1,7 @@
use error::{ErrorKind, Result};
use ethabi::{Address, FixedBytes, Log, RawTopicFilter, Token, Topic, Uint};
use ethabi::{Address, Log, RawTopicFilter, Token, Topic, Uint};
use std::fmt;
use util::{HexBytes, LogExt};
/// An event that is logged when the current set of validators has changed.
#[derive(Debug)]
pub struct ChangeFinalized {
/// The new set of validators.
pub new_set: Vec<Address>,
}
impl ChangeFinalized {
/// Parses the log and returns a `ChangeFinalized`, if the log corresponded to such an event.
pub fn from_log(log: &Log) -> Result<ChangeFinalized> {
log.param(0, "newSet")
.cloned()
.and_then(Token::to_array)
.map(|tokens| ChangeFinalized {
new_set: tokens.into_iter().filter_map(Token::to_address).collect(),
})
.ok_or_else(|| ErrorKind::UnexpectedLogParams.into())
}
}
impl fmt::Display for ChangeFinalized {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ChangeFinalized {{ new_set: [",)?;
for (i, voter) in self.new_set.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", voter)?;
}
write!(f, "] }}")
}
}
#[derive(Debug)]
pub struct InitiateChange {
/// The previous voter set's hash.
parent_hash: FixedBytes,
/// The new set of validators.
pub new_set: Vec<Address>,
}
impl InitiateChange {
/// Parses the log and returns a `InitiateChange`, if the log corresponded to such an event.
pub fn from_log(log: &Log) -> Result<InitiateChange> {
match (
log.param(0, "parentHash")
.cloned()
.and_then(Token::to_fixed_bytes),
log.param(1, "newSet").cloned().and_then(Token::to_array),
) {
(Some(parent_hash), Some(tokens)) => Ok(InitiateChange {
parent_hash,
new_set: tokens.into_iter().filter_map(Token::to_address).collect(),
}),
_ => Err(ErrorKind::UnexpectedLogParams.into()),
}
}
}
impl fmt::Display for InitiateChange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"InitiateChange {{ parent_hash: {}, new_set: [",
HexBytes(&self.parent_hash)
)?;
for (i, voter) in self.new_set.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", voter)?;
}
write!(f, "] }}")
}
}
use util::LogExt;
/// An event that is logged when a new ballot is started.
#[derive(Debug)]

View File

@ -3,6 +3,10 @@ extern crate colored;
#[macro_use]
extern crate error_chain;
extern crate ethabi;
#[macro_use]
extern crate ethabi_derive;
#[macro_use]
extern crate ethabi_contract;
extern crate serde;
#[macro_use]
extern crate serde_derive;
@ -17,19 +21,23 @@ mod util;
mod validator;
use error::{Error, ErrorKind};
use events::{BallotCreated, ChangeFinalized, InitiateChange, Vote};
use events::{BallotCreated, Vote};
use stats::Stats;
use std::default::Default;
use std::fs::File;
use util::{ContractExt, TopicFilterExt, Web3LogExt};
use util::{ContractExt, HexBytes, HexList, TopicFilterExt, Web3LogExt};
use web3::futures::Future;
// TODO: `ethabi_derive` produces unparseable tokens.
// mod voting_to_change_keys {
// #[derive(EthabiContract)]
// #[ethabi_contract_options(name = "VotingToChangeKeys", path = "abi/VotingToChangeKeys.json")]
// struct _Dummy;
// }
use_contract!(
net_con,
"NetworkConsensus",
"abi/PoaNetworkConsensus.abi.json"
);
// use_contract!(
// voting,
// "VotingToChangeKeys",
// "abi/VotingToChangeKeys.abi.json"
// );
#[derive(Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
@ -48,12 +56,11 @@ fn count_votes(
let web3 = web3::Web3::new(transport);
let voting_abi = File::open("abi/VotingToChangeKeys.abi.json").expect("read voting abi");
let net_con_abi = File::open("abi/PoaNetworkConsensus.abi.json").expect("read consensus 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");
let voting_contract = ethabi::Contract::load(voting_abi)?;
let net_con_contract = ethabi::Contract::load(net_con_abi)?;
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)?;
@ -64,18 +71,16 @@ fn count_votes(
let ballot_event = voting_contract.event("BallotCreated")?;
let vote_event = voting_contract.event("Vote")?;
let change_event = net_con_contract.event("ChangeFinalized")?;
let init_change_event = net_con_contract.event("InitiateChange")?;
let change_event = net_con_contract.events().change_finalized();
let init_change_event = net_con_contract.events().initiate_change();
// Find all ballots and voter changes.
let ballot_or_change_filter = ethabi::TopicFilter {
topic0: ethabi::Topic::OneOf(vec![
ballot_event.signature(),
change_event.signature(),
init_change_event.signature(),
]),
..ethabi::TopicFilter::default()
}.to_filter_builder()
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()
.build();
let ballot_change_logs_filter = web3.eth_filter()
.create_logs_filter(ballot_or_change_filter)
@ -84,7 +89,7 @@ fn count_votes(
// 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();
let mut prev_init_change: Option<InitiateChange> = None;
let mut prev_init_change: Option<net_con::logs::InitiateChange> = None;
if verbose {
println!("Collecting events…");
@ -94,18 +99,23 @@ fn count_votes(
// Iterate over all ballot and voter change events.
for log in ballot_change_logs_filter.logs().wait()? {
event_found = true;
if let Ok(change_log) = change_event.parse_log(log.clone().into_raw()) {
if let Ok(change) = change_event.parse_log(log.clone().into_raw()) {
// If it is a `ChangeFinalized`, update the current set of voters.
let change = ChangeFinalized::from_log(&change_log)?;
if verbose {
println!("{}", change);
println!(
"• ChangeFinalized {{ new_set: {} }}",
HexList(&change.new_set)
);
}
voters = change.new_set;
} else if let Ok(init_change_log) = init_change_event.parse_log(log.clone().into_raw()) {
} 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.
let init_change = InitiateChange::from_log(&init_change_log)?;
if verbose {
println!("{}", init_change);
println!(
"• InitiateChange {{ parent_hash: {}, new_set: {} }}",
HexBytes(&init_change.parent_hash),
HexList(&init_change.new_set)
);
}
if let Some(prev) = prev_init_change.take() {
voters = vec![];

View File

@ -53,23 +53,73 @@ impl ContractExt for web3::contract::Contract<web3::transports::Http> {
}
}
trait TopicExt<T> {
/// Returns the union of the two topics.
fn or(self, other: Self) -> Self;
/// Converts this topic into an `Option<Vec<T>>`, where `Any` corresponds to `None`,
/// `This` to a vector with one element, and `OneOf` to any vector.
fn to_opt_vec(self) -> Option<Vec<T>>;
}
impl<T: Ord> TopicExt<T> for ethabi::Topic<T> {
fn or(self, other: Self) -> Self {
match (self.to_opt_vec(), other.to_opt_vec()) {
(Some(mut v0), Some(v1)) => {
for e in v1 {
if !v0.contains(&e) {
v0.push(e);
}
}
if v0.len() == 1 {
ethabi::Topic::This(v0.into_iter().next().expect("has a single element; qed"))
} else {
ethabi::Topic::OneOf(v0)
}
}
(_, _) => ethabi::Topic::Any,
}
}
fn to_opt_vec(self) -> Option<Vec<T>> {
match self {
ethabi::Topic::Any => None,
ethabi::Topic::OneOf(v) => Some(v),
ethabi::Topic::This(t) => Some(vec![t]),
}
}
}
pub trait TopicFilterExt {
/// Returns a `web3::types::FilterBuilder` with these topics, starting from the first block.
fn to_filter_builder(self) -> web3::types::FilterBuilder;
/// 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;
}
impl TopicFilterExt for ethabi::TopicFilter {
fn to_filter_builder(self) -> web3::types::FilterBuilder {
web3::types::FilterBuilder::default()
.topics(
to_topic(self.topic0),
to_topic(self.topic1),
to_topic(self.topic2),
to_topic(self.topic3),
self.topic0.to_opt_vec(),
self.topic1.to_opt_vec(),
self.topic2.to_opt_vec(),
self.topic3.to_opt_vec(),
)
.from_block(web3::types::BlockNumber::Earliest)
.to_block(web3::types::BlockNumber::Latest)
}
fn or(self, other: ethabi::TopicFilter) -> ethabi::TopicFilter {
ethabi::TopicFilter {
topic0: self.topic0.or(other.topic0),
topic1: self.topic1.or(other.topic1),
topic2: self.topic2.or(other.topic2),
topic3: self.topic3.or(other.topic3),
}
}
}
pub trait Web3LogExt {
@ -82,16 +132,6 @@ impl Web3LogExt for web3::types::Log {
}
}
/// Converts an `ethabi::Topic<T>` into an `Option<Vec<T>>`, where `Any` corresponds to `None`,
/// `This` to a vector with one element, and `OneOf` to any vector.
fn to_topic<T>(topic: ethabi::Topic<T>) -> Option<Vec<T>> {
match topic {
ethabi::Topic::Any => None,
ethabi::Topic::OneOf(v) => Some(v),
ethabi::Topic::This(t) => Some(vec![t]),
}
}
pub trait LogExt {
/// Returns the `i`-th parameter, if it has the given name, otherwise `None`.
fn param(&self, i: usize, name: &str) -> Option<&ethabi::Token>;
@ -146,3 +186,23 @@ impl<'a> fmt::Display for HexBytes<'a> {
Ok(())
}
}
/// Wrapper for a list of byte arrays, whose `Display` implementation outputs shortened hexadecimal
/// strings.
pub struct HexList<'a, T: 'a>(pub &'a [T]);
impl<'a, T: 'a> fmt::Display for HexList<'a, T>
where
T: AsRef<[u8]>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[")?;
for (i, item) in self.0.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", HexBytes(item.as_ref()))?;
}
write!(f, "]")
}
}