/
main.rs
204 lines (183 loc) · 7.5 KB
/
main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
extern crate clap;
extern crate colored;
#[macro_use]
extern crate error_chain;
extern crate ethabi;
#[macro_use(EthabiContract)]
extern crate ethabi_derive;
#[macro_use(use_contract)]
extern crate ethabi_contract;
extern crate serde;
#[macro_use(Deserialize)]
extern crate serde_derive;
extern crate serde_json;
extern crate web3;
mod cli;
mod error;
mod stats;
mod util;
mod validator;
use error::{Error, ErrorKind};
use ethabi::Address;
use stats::Stats;
use std::default::Default;
use std::fs::File;
use std::time::{SystemTime, UNIX_EPOCH};
use util::{HexBytes, HexList, TopicFilterExt, Web3LogExt};
use web3::futures::Future;
/// The maximum age in seconds of the latest block.
const MAX_BLOCK_AGE: u64 = 60 * 60;
// The `use_contract!` macro triggers several Clippy warnings.
#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments, redundant_closure, needless_update))]
mod contracts {
use_contract!(
net_con,
"NetworkConsensus",
"abi/PoaNetworkConsensus.abi.json"
);
use_contract!(
voting,
"VotingToChangeKeys",
"abi/VotingToChangeKeys.abi.json"
);
use_contract!(
val_meta,
"ValidatorMetadata",
"abi/ValidatorMetadata.abi.json"
);
use_contract!(key_mgr, "KeysManager", "abi/KeysManager.abi.json");
}
use contracts::*;
#[derive(Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
struct ContractAddresses {
metadata_address: String,
keys_manager_address: String,
}
/// Shows a warning if the node's latest block is outdated.
fn check_synced<T: web3::Transport>(web3: &web3::Web3<T>) {
let id = web3::types::BlockId::Number(web3::types::BlockNumber::Latest);
let block = web3.eth().block(id).wait().expect("get latest block");
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Current timestamp is earlier than the Unix epoch!");
if block.timestamp < (now.as_secs() - MAX_BLOCK_AGE).into() {
eprintln!("WARNING: The node is not fully synchronized. Stats may be inaccurate.");
}
}
/// Finds all logged ballots and returns statistics about how many were missed by each voter.
fn count_votes(
url: &str,
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);
check_synced(&web3);
let voting_contract = voting::VotingToChangeKeys::default();
let net_con_contract = net_con::NetworkConsensus::default();
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).expect("parse contract address");
let key_mgr_addr =
util::parse_address(&contract_addrs.keys_manager_address).expect("parse contract address");
let ballot_event = voting_contract.events().ballot_created();
let vote_event = voting_contract.events().vote();
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 = (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<Address> = Vec::new();
let mut stats = Stats::default();
let mut prev_init_change: Option<net_con::logs::InitiateChange> = None;
vprintln!("Collecting events…");
let mut event_found = false;
// Iterate over all ballot and voter change events.
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.
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.
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 = get_voting_by_mining_fn.call(mining_key, &*raw_call)?;
if voter != Address::zero() {
voters.push(voter);
}
}
}
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.
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) {
vprintln!(" Unexpected voter {}", vote.voter);
voters.push(vote.voter);
}
Ok(vote)
})
.collect::<Result<Vec<_>, Error>>()?;
stats.add_ballot(&voters, &votes);
} else {
return Err(ErrorKind::UnexpectedLogParams.into());
}
}
if !event_found {
return Err(ErrorKind::NoEventsFound.into());
}
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 get_mining_by_voting_key_fn.call(voter, &*raw_call) {
Err(err) => {
eprintln!("Failed to find mining key for voter {}: {:?}", voter, err);
continue;
}
Ok(key) => key,
};
let validator = validators_fn.call(mining_key, &*raw_call)?.into();
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");
let contract_file = matches
.value_of("contracts")
.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");
let stats = count_votes(url, verbose, &contract_addrs).expect("count votes");
println!("{}", stats);
}