detect changes in safe_contract
This commit is contained in:
parent
34a1512ff0
commit
b4f3e30cd6
@ -27,7 +27,9 @@ const SERVICE_TRANSACTION_ABI: &'static str = r#"[{"constant":false,"inputs":[{"
|
|||||||
|
|
||||||
const SECRETSTORE_ACL_STORAGE_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"document","type":"bytes32"}],"name":"checkPermissions","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}]"#;
|
const SECRETSTORE_ACL_STORAGE_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"document","type":"bytes32"}],"name":"checkPermissions","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}]"#;
|
||||||
|
|
||||||
const VALIDATOR_SET_ABI: &'static str = r#"[{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]"#;
|
// be very careful changing these: ensure `ethcore/engines` validator sets have corresponding
|
||||||
|
// changes.
|
||||||
|
const VALIDATOR_SET_ABI: &'static str = r#"[{"constant":true,"inputs":[],"name":"transitionNonce","outputs":[{"name":"nonce","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"validators","type":"address[]"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_parent_hash","type":"bytes32"},{"indexed":true,"name":"_nonce","type":"uint256"},{"indexed":false,"name":"_new_set","type":"address[]"}],"name":"ValidatorsChanged","type":"event"}]"#;
|
||||||
|
|
||||||
const VALIDATOR_REPORT_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"}]"#;
|
const VALIDATOR_REPORT_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"}]"#;
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ pub fn generate_module(struct_name: &str, abi: &str) -> Result<String, Error> {
|
|||||||
Ok(format!(r##"
|
Ok(format!(r##"
|
||||||
use byteorder::{{BigEndian, ByteOrder}};
|
use byteorder::{{BigEndian, ByteOrder}};
|
||||||
use futures::{{future, Future, IntoFuture, BoxFuture}};
|
use futures::{{future, Future, IntoFuture, BoxFuture}};
|
||||||
use ethabi::{{Contract, Interface, Token}};
|
use ethabi::{{Contract, Interface, Token, Event}};
|
||||||
use util::{{self, Uint}};
|
use util::{{self, Uint}};
|
||||||
|
|
||||||
pub struct {name} {{
|
pub struct {name} {{
|
||||||
@ -70,6 +70,11 @@ impl {name} {{
|
|||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
/// Access the underlying `ethabi` contract.
|
||||||
|
pub fn contract(this: &Self) -> &Contract {{
|
||||||
|
&this.contract
|
||||||
|
}}
|
||||||
|
|
||||||
{functions}
|
{functions}
|
||||||
}}
|
}}
|
||||||
"##,
|
"##,
|
||||||
|
@ -23,16 +23,25 @@ use native_contracts::ValidatorSet as Provider;
|
|||||||
use util::*;
|
use util::*;
|
||||||
use util::cache::MemoryLruCache;
|
use util::cache::MemoryLruCache;
|
||||||
|
|
||||||
use engines::Call;
|
use basic_types::LogBloom;
|
||||||
use types::ids::BlockId;
|
|
||||||
use client::{Client, BlockChainClient};
|
use client::{Client, BlockChainClient};
|
||||||
|
use engines::Call;
|
||||||
use header::Header;
|
use header::Header;
|
||||||
|
use ids::BlockId;
|
||||||
|
use log_entry::LogEntry;
|
||||||
|
|
||||||
use super::ValidatorSet;
|
use super::ValidatorSet;
|
||||||
use super::simple_list::SimpleList;
|
use super::simple_list::SimpleList;
|
||||||
|
|
||||||
const MEMOIZE_CAPACITY: usize = 500;
|
const MEMOIZE_CAPACITY: usize = 500;
|
||||||
|
|
||||||
|
// TODO: ethabi should be able to generate this.
|
||||||
|
const EVENT_NAME: &'static [u8] = &*b"ValidatorsChanged(bytes32,uint256,address[])";
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref EVENT_NAME_HASH: H256 = EVENT_NAME.sha3();
|
||||||
|
}
|
||||||
|
|
||||||
/// The validator contract should have the following interface:
|
/// The validator contract should have the following interface:
|
||||||
pub struct ValidatorSafeContract {
|
pub struct ValidatorSafeContract {
|
||||||
pub address: Address,
|
pub address: Address,
|
||||||
@ -41,6 +50,14 @@ pub struct ValidatorSafeContract {
|
|||||||
client: RwLock<Option<Weak<Client>>>, // TODO [keorn]: remove
|
client: RwLock<Option<Weak<Client>>>, // TODO [keorn]: remove
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encode_proof(nonce: U256, validators: &[Address]) -> Bytes {
|
||||||
|
use rlp::RlpStream;
|
||||||
|
|
||||||
|
let mut stream = RlpStream::new_list(2);
|
||||||
|
stream.append(&nonce).append_list(validators);
|
||||||
|
stream.drain().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
impl ValidatorSafeContract {
|
impl ValidatorSafeContract {
|
||||||
pub fn new(contract_address: Address) -> Self {
|
pub fn new(contract_address: Address) -> Self {
|
||||||
ValidatorSafeContract {
|
ValidatorSafeContract {
|
||||||
@ -64,6 +81,40 @@ impl ValidatorSafeContract {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Queries for the current validator set transition nonce.
|
||||||
|
fn get_nonce(&self, caller: &Call) -> Option<::util::U256> {
|
||||||
|
match self.provider.transition_nonce(caller).wait() {
|
||||||
|
Ok(nonce) => Some(nonce),
|
||||||
|
Err(s) => {
|
||||||
|
debug!(target: "engine", "Unable to fetch transition nonce: {}", s);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether the header matches the expected bloom.
|
||||||
|
//
|
||||||
|
// The expected log should have 3 topics:
|
||||||
|
// 1. ETHABI-encoded log name.
|
||||||
|
// 2. the block's parent hash.
|
||||||
|
// 3. the "nonce": n for the nth transition in history.
|
||||||
|
//
|
||||||
|
// We can only search for the first 2, since we don't have the third
|
||||||
|
// just yet.
|
||||||
|
//
|
||||||
|
// The parent hash is included to prevent
|
||||||
|
// malicious actors from brute forcing other logs that would
|
||||||
|
// produce the same bloom.
|
||||||
|
//
|
||||||
|
// The log data is an array of all new validator addresses.
|
||||||
|
fn expected_bloom(&self, header: &Header) -> LogBloom {
|
||||||
|
LogEntry {
|
||||||
|
address: self.address,
|
||||||
|
topics: vec![*EVENT_NAME_HASH, *header.parent_hash()],
|
||||||
|
data: Vec::new(), // irrelevant for bloom.
|
||||||
|
}.bloom()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValidatorSet for ValidatorSafeContract {
|
impl ValidatorSet for ValidatorSafeContract {
|
||||||
@ -75,17 +126,80 @@ impl ValidatorSet for ValidatorSafeContract {
|
|||||||
.and_then(|c| c.call_contract(id, addr, data)))
|
.and_then(|c| c.call_contract(id, addr, data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn proof_required(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
|
fn proof_required(&self, header: &Header, _block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
|
||||||
-> ::engines::RequiresProof
|
-> ::engines::RequiresProof
|
||||||
{
|
{
|
||||||
// TODO: check blooms first and then logs for the
|
let bloom = self.expected_bloom(header);
|
||||||
// ValidatorsChanged([parent_hash, nonce], new_validators) log event.
|
let header_bloom = header.log_bloom();
|
||||||
|
|
||||||
|
if &bloom & header_bloom != bloom { return ::engines::RequiresProof::No }
|
||||||
|
|
||||||
|
match receipts {
|
||||||
|
None => ::engines::RequiresProof::Unsure(::engines::Unsure::NeedsReceipts),
|
||||||
|
Some(receipts) => {
|
||||||
|
let check_log = |log: &LogEntry| {
|
||||||
|
log.address == self.address &&
|
||||||
|
log.topics.len() == 3 &&
|
||||||
|
log.topics[0] == *EVENT_NAME_HASH &&
|
||||||
|
log.topics[1] == *header.parent_hash()
|
||||||
|
// don't have anything to compare nonce to yet.
|
||||||
|
};
|
||||||
|
|
||||||
|
let event = Provider::contract(&self.provider)
|
||||||
|
.event("ValidatorsChanged".into())
|
||||||
|
.expect("Contract known ahead of time to have `ValidatorsChanged` event; qed");
|
||||||
|
|
||||||
|
let mut decoded_events = receipts.iter()
|
||||||
|
.filter(|r| &bloom & &r.log_bloom == bloom)
|
||||||
|
.flat_map(|r| r.logs.iter())
|
||||||
|
.filter(move |l| check_log(l))
|
||||||
|
.filter_map(|log| {
|
||||||
|
let topics = log.topics.iter().map(|x| x.0.clone()).collect();
|
||||||
|
match event.decode_log(topics, log.data.clone()) {
|
||||||
|
Ok(decoded) => Some(decoded),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: are multiple transitions per block possible?
|
||||||
|
match decoded_events.next() {
|
||||||
|
None => ::engines::RequiresProof::No,
|
||||||
|
Some(matched_event) => {
|
||||||
|
// decode log manually until the native contract generator is
|
||||||
|
// good enough to do it for us.
|
||||||
|
let &(_, _, ref nonce_token) = &matched_event.params[2];
|
||||||
|
let &(_, _, ref validators_token) = &matched_event.params[3];
|
||||||
|
|
||||||
|
let nonce: Option<U256> = nonce_token.clone().to_uint()
|
||||||
|
.map(H256).map(Into::into);
|
||||||
|
let validators = validators_token.clone().to_array()
|
||||||
|
.and_then(|a| a.into_iter()
|
||||||
|
.map(|x| x.to_address().map(H160))
|
||||||
|
.collect::<Option<Vec<_>>>()
|
||||||
|
);
|
||||||
|
|
||||||
|
match (nonce, validators) {
|
||||||
|
(Some(nonce), Some(validators)) =>
|
||||||
|
::engines::RequiresProof::Yes(Some(encode_proof(nonce, &validators))),
|
||||||
|
_ => {
|
||||||
|
debug!(target: "engine", "Successfully decoded log turned out to be bad.");
|
||||||
::engines::RequiresProof::No
|
::engines::RequiresProof::No
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_proof(&self, header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
|
// the proof we generate is an RLP list containing two parts.
|
||||||
self.get_list(caller).map(|list| ::rlp::encode_list(&list.into_inner()).to_vec())
|
// (nonce, validators)
|
||||||
.ok_or_else(|| "Caller insufficient to get validator list.".into())
|
fn generate_proof(&self, _header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
|
||||||
|
match (self.get_nonce(caller), self.get_list(caller)) {
|
||||||
|
(Some(nonce), Some(list)) => Ok(encode_proof(nonce, &list.into_inner())),
|
||||||
|
_ => Err("Caller insufficient to generate validator proof.".into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains_with_caller(&self, block_hash: &H256, address: &Address, caller: &Call) -> bool {
|
fn contains_with_caller(&self, block_hash: &H256, address: &Address, caller: &Call) -> bool {
|
||||||
|
Loading…
Reference in New Issue
Block a user