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 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"}]"#;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,7 @@ pub fn generate_module(struct_name: &str, abi: &str) -> Result<String, Error> {
 | 
			
		||||
	Ok(format!(r##"
 | 
			
		||||
use byteorder::{{BigEndian, ByteOrder}};
 | 
			
		||||
use futures::{{future, Future, IntoFuture, BoxFuture}};
 | 
			
		||||
use ethabi::{{Contract, Interface, Token}};
 | 
			
		||||
use ethabi::{{Contract, Interface, Token, Event}};
 | 
			
		||||
use util::{{self, Uint}};
 | 
			
		||||
 | 
			
		||||
pub struct {name} {{
 | 
			
		||||
@ -70,6 +70,11 @@ impl {name} {{
 | 
			
		||||
		}}
 | 
			
		||||
	}}
 | 
			
		||||
 | 
			
		||||
	/// Access the underlying `ethabi` contract.
 | 
			
		||||
	pub fn contract(this: &Self) -> &Contract {{
 | 
			
		||||
		&this.contract
 | 
			
		||||
	}}
 | 
			
		||||
 | 
			
		||||
	{functions}
 | 
			
		||||
}}
 | 
			
		||||
"##,
 | 
			
		||||
 | 
			
		||||
@ -23,16 +23,25 @@ use native_contracts::ValidatorSet as Provider;
 | 
			
		||||
use util::*;
 | 
			
		||||
use util::cache::MemoryLruCache;
 | 
			
		||||
 | 
			
		||||
use engines::Call;
 | 
			
		||||
use types::ids::BlockId;
 | 
			
		||||
use basic_types::LogBloom;
 | 
			
		||||
use client::{Client, BlockChainClient};
 | 
			
		||||
use engines::Call;
 | 
			
		||||
use header::Header;
 | 
			
		||||
use ids::BlockId;
 | 
			
		||||
use log_entry::LogEntry;
 | 
			
		||||
 | 
			
		||||
use super::ValidatorSet;
 | 
			
		||||
use super::simple_list::SimpleList;
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
pub struct ValidatorSafeContract {
 | 
			
		||||
	pub address: Address,
 | 
			
		||||
@ -41,6 +50,14 @@ pub struct ValidatorSafeContract {
 | 
			
		||||
	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 {
 | 
			
		||||
	pub fn new(contract_address: Address) -> Self {
 | 
			
		||||
		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 {
 | 
			
		||||
@ -75,17 +126,80 @@ impl ValidatorSet for ValidatorSafeContract {
 | 
			
		||||
			.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
 | 
			
		||||
	{
 | 
			
		||||
		// TODO: check blooms first and then logs for the
 | 
			
		||||
		// ValidatorsChanged([parent_hash, nonce], new_validators) log event.
 | 
			
		||||
		::engines::RequiresProof::No
 | 
			
		||||
		let bloom = self.expected_bloom(header);
 | 
			
		||||
		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
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn generate_proof(&self, header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
 | 
			
		||||
		self.get_list(caller).map(|list| ::rlp::encode_list(&list.into_inner()).to_vec())
 | 
			
		||||
			.ok_or_else(|| "Caller insufficient to get validator list.".into())
 | 
			
		||||
	// the proof we generate is an RLP list containing two parts.
 | 
			
		||||
	//   (nonce, validators)
 | 
			
		||||
	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 {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user