PoA: Wait for transition finality before applying (#5774)

* final engine changes

* migration to v13

* adding and removing pending transitions

* epoch_transition_for

* port snapshot to new engine methods

* final validator set interface

* fix compiler errors

* revert v13/epoch_depth transition

* make call on new epoch

* rolling finality checker

* tests for finality checker

* constructing finality proof upon pending transition

* fix warnings and finality proof checking

* fix compiler warnings in tests

* test fixes

* don't include genesis in finality checking

* change snapshot test chain building logic

* minor refactorings

* fetch epoch transition based on parent, fix divide-by-zero in SimpleList

* fix formatting

* fix ABIs and finality checking in snapshot restoration

* encode signal number in proof

* create more blocks at the end of tests

* update gist to accurate contract code

* test for epoch_transition_for

* fix tests with immediateTransitions parameter

* disable force flag after forcing

* rename ValidatorsChanged to InitiateChange and finalizeSignal to finalizeChange

* a few more validator set tests
This commit is contained in:
Robert Habermeier
2017-06-28 13:17:36 +02:00
committed by Arkadiy Paronyan
parent 3637c14da5
commit d069b98b45
41 changed files with 1905 additions and 804 deletions

View File

@@ -24,10 +24,10 @@ use futures::Future;
use native_contracts::ValidatorReport as Provider;
use client::{Client, BlockChainClient};
use engines::Call;
use engines::{Call, Engine};
use header::{Header, BlockNumber};
use super::ValidatorSet;
use super::{ValidatorSet, SimpleList, SystemCall};
use super::safe_contract::ValidatorSafeContract;
/// A validator contract with reporting.
@@ -51,7 +51,7 @@ impl ValidatorContract {
// could be `impl Trait`.
// note: dispatches transactions to network as well as execute.
// TODO [keorn]: Make more general.
fn transact(&self) -> Box<Call> {
fn transact(&self) -> Box<Fn(Address, Bytes) -> Result<Bytes, String>> {
let client = self.client.read().clone();
Box::new(move |a, d| client.as_ref()
.and_then(Weak::upgrade)
@@ -66,18 +66,30 @@ impl ValidatorSet for ValidatorContract {
self.validators.default_caller(id)
}
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> ::engines::EpochChange
{
self.validators.is_epoch_end(header, block, receipts)
fn on_epoch_begin(&self, first: bool, header: &Header, call: &mut SystemCall) -> Result<(), ::error::Error> {
self.validators.on_epoch_begin(first, header, call)
}
fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
self.validators.epoch_proof(header, caller)
fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result<Vec<u8>, String> {
self.validators.genesis_epoch_data(header, call)
}
fn epoch_set(&self, header: &Header, proof: &[u8]) -> Result<(u64, super::SimpleList), ::error::Error> {
self.validators.epoch_set(header, proof)
fn is_epoch_end(&self, first: bool, chain_head: &Header) -> Option<Vec<u8>> {
self.validators.is_epoch_end(first, chain_head)
}
fn signals_epoch_end(
&self,
first: bool,
header: &Header,
block: Option<&[u8]>,
receipts: Option<&[::receipt::Receipt]>,
) -> ::engines::EpochChange {
self.validators.signals_epoch_end(first, header, block, receipts)
}
fn epoch_set(&self, first: bool, engine: &Engine, number: BlockNumber, proof: &[u8]) -> Result<(SimpleList, Option<H256>), ::error::Error> {
self.validators.epoch_set(first, engine, number, proof)
}
fn contains_with_caller(&self, bh: &H256, address: &Address, caller: &Call) -> bool {
@@ -92,14 +104,14 @@ impl ValidatorSet for ValidatorContract {
self.validators.count_with_caller(bh, caller)
}
fn report_malicious(&self, address: &Address, block: BlockNumber, proof: Bytes) {
fn report_malicious(&self, address: &Address, _set_block: BlockNumber, block: BlockNumber, proof: Bytes) {
match self.provider.report_malicious(&*self.transact(), *address, block.into(), proof).wait() {
Ok(_) => warn!(target: "engine", "Reported malicious validator {}", address),
Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s),
}
}
fn report_benign(&self, address: &Address, block: BlockNumber) {
fn report_benign(&self, address: &Address, _set_block: BlockNumber, block: BlockNumber) {
match self.provider.report_benign(&*self.transact(), *address, block.into()).wait() {
Ok(_) => warn!(target: "engine", "Reported benign validator misbehaviour {}", address),
Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s),

View File

@@ -37,7 +37,10 @@ use self::contract::ValidatorContract;
use self::safe_contract::ValidatorSafeContract;
use self::multi::Multi;
use super::Call;
use super::{Call, Engine};
/// A system-calling closure. Enacts calls on a block's state from the system address.
pub type SystemCall<'a> = FnMut(Address, Bytes) -> Result<Bytes, String> + 'a;
/// Creates a validator set from spec.
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> {
@@ -64,6 +67,7 @@ pub trait ValidatorSet: Send + Sync {
let default = self.default_caller(BlockId::Hash(*parent));
self.contains_with_caller(parent, address, &*default)
}
/// Draws an validator nonce modulo number of validators.
fn get(&self, parent: &H256, nonce: usize) -> Address {
let default = self.default_caller(BlockId::Hash(*parent));
@@ -76,48 +80,66 @@ pub trait ValidatorSet: Send + Sync {
self.count_with_caller(parent, &*default)
}
/// Signalling that a new epoch has begun.
///
/// All calls here will be from the `SYSTEM_ADDRESS`: 2^160 - 2
/// and will have an effect on the block's state.
/// The caller provided here may not generate proofs.
///
/// `first` is true if this is the first block in the set.
fn on_epoch_begin(&self, _first: bool, _header: &Header, _call: &mut SystemCall) -> Result<(), ::error::Error> {
Ok(())
}
/// Extract genesis epoch data from the genesis state and header.
fn genesis_epoch_data(&self, _header: &Header, _call: &Call) -> Result<Vec<u8>, String> { Ok(Vec::new()) }
/// Whether this block is the last one in its epoch.
/// Usually indicates that the validator set changed at the given block.
///
/// Should not inspect state! This is used in situations where
/// state is not generally available.
/// Indicates that the validator set changed at the given block in a manner
/// that doesn't require finality.
///
/// Return `Yes` or `No` indicating whether it changed at the given header,
/// or `Unsure` indicating a need for more information.
///
/// If block or receipts are provided, do not return `Unsure` indicating
/// need for them.
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> super::EpochChange;
/// `first` is true if this is the first block in the set.
fn is_epoch_end(&self, first: bool, chain_head: &Header) -> Option<Vec<u8>>;
/// Generate epoch proof.
/// Must interact with state only through the given caller!
/// Otherwise, generated proofs may be wrong.
fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Vec<u8>, String>;
/// Whether the given block signals the end of an epoch, but change won't take effect
/// until finality.
///
/// Engine should set `first` only if the header is genesis. Multiplexing validator
/// sets can set `first` to internal changes.
fn signals_epoch_end(
&self,
first: bool,
header: &Header,
block: Option<&[u8]>,
receipts: Option<&[::receipt::Receipt]>,
) -> ::engines::EpochChange;
/// Recover the validator set for all
/// Recover the validator set from the given proof, the block number, and
/// whether this header is first in its set.
///
/// May fail if the given header doesn't kick off an epoch or
/// the proof is invalid.
///
/// Returns the epoch number and proof.
fn epoch_set(&self, header: &Header, proof: &[u8]) -> Result<(u64, SimpleList), ::error::Error>;
/// Returns the set, along with a flag indicating whether finality of a specific
/// hash should be proven.
fn epoch_set(&self, first: bool, engine: &Engine, number: BlockNumber, proof: &[u8])
-> Result<(SimpleList, Option<H256>), ::error::Error>;
/// Checks if a given address is a validator, with the given function
/// for executing synchronous calls to contracts.
fn contains_with_caller(&self, parent_block_hash: &H256, address: &Address, caller: &Call) -> bool;
/// Draws an validator nonce modulo number of validators.
///
fn get_with_caller(&self, parent_block_hash: &H256, nonce: usize, caller: &Call) -> Address;
/// Returns the current number of validators.
fn count_with_caller(&self, parent_block_hash: &H256, caller: &Call) -> usize;
/// Notifies about malicious behaviour.
fn report_malicious(&self, _validator: &Address, _block: BlockNumber, _proof: Bytes) {}
fn report_malicious(&self, _validator: &Address, _set_block: BlockNumber, _block: BlockNumber, _proof: Bytes) {}
/// Notifies about benign misbehaviour.
fn report_benign(&self, _validator: &Address, _block: BlockNumber) {}
fn report_benign(&self, _validator: &Address, _set_block: BlockNumber, _block: BlockNumber) {}
/// Allows blockchain state access.
fn register_contract(&self, _client: Weak<Client>) {}
}

View File

@@ -18,12 +18,12 @@
use std::collections::BTreeMap;
use std::sync::Weak;
use engines::{Call, EpochChange};
use engines::{Call, Engine};
use util::{Bytes, H256, Address, RwLock};
use ids::BlockId;
use header::{BlockNumber, Header};
use client::{Client, BlockChainClient};
use super::ValidatorSet;
use super::{SystemCall, ValidatorSet};
type BlockNumberLookup = Box<Fn(BlockId) -> Result<BlockNumber, String> + Send + Sync + 'static>;
@@ -72,49 +72,38 @@ impl ValidatorSet for Multi {
.unwrap_or(Box::new(|_, _| Err("No validator set for given ID.".into())))
}
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> EpochChange
fn on_epoch_begin(&self, _first: bool, header: &Header, call: &mut SystemCall) -> Result<(), ::error::Error> {
let (set_block, set) = self.correct_set_by_number(header.number());
let first = set_block == header.number();
set.on_epoch_begin(first, header, call)
}
fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result<Vec<u8>, String> {
self.correct_set_by_number(0).1.genesis_epoch_data(header, call)
}
fn is_epoch_end(&self, _first: bool, chain_head: &Header) -> Option<Vec<u8>> {
let (set_block, set) = self.correct_set_by_number(chain_head.number());
let first = set_block == chain_head.number();
set.is_epoch_end(first, chain_head)
}
fn signals_epoch_end(&self, _first: bool, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> ::engines::EpochChange
{
let (set_block, set) = self.correct_set_by_number(header.number());
let (next_set_block, _) = self.correct_set_by_number(header.number() + 1);
let first = set_block == header.number();
// multi-set transitions require epoch changes.
if next_set_block != set_block {
return EpochChange::Yes(next_set_block);
}
match set.is_epoch_end(header, block, receipts) {
EpochChange::Yes(num) => EpochChange::Yes(set_block + num),
other => other,
}
set.signals_epoch_end(first, header, block, receipts)
}
fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
let (set_block, set) = self.correct_set_by_number(header.number());
let (next_set_block, next_set) = self.correct_set_by_number(header.number() + 1);
fn epoch_set(&self, _first: bool, engine: &Engine, number: BlockNumber, proof: &[u8]) -> Result<(super::SimpleList, Option<H256>), ::error::Error> {
let (set_block, set) = self.correct_set_by_number(number);
let first = set_block == number;
if next_set_block != set_block {
return next_set.epoch_proof(header, caller);
}
set.epoch_proof(header, caller)
}
fn epoch_set(&self, header: &Header, proof: &[u8]) -> Result<(u64, super::SimpleList), ::error::Error> {
// "multi" epoch is the inner set's epoch plus the transition block to that set.
// ensures epoch increases monotonically.
let (set_block, set) = self.correct_set_by_number(header.number());
let (next_set_block, next_set) = self.correct_set_by_number(header.number() + 1);
// this block kicks off a new validator set -- get the validator set
// starting there.
if next_set_block != set_block {
let (inner_epoch, list) = next_set.epoch_set(header, proof)?;
Ok((next_set_block + inner_epoch, list))
} else {
let (inner_epoch, list) = set.epoch_set(header, proof)?;
Ok((set_block + inner_epoch, list))
}
set.epoch_set(first, engine, number, proof)
}
fn contains_with_caller(&self, bh: &H256, address: &Address, caller: &Call) -> bool {
@@ -132,12 +121,12 @@ impl ValidatorSet for Multi {
.map_or_else(usize::max_value, |set| set.count_with_caller(bh, caller))
}
fn report_malicious(&self, validator: &Address, block: BlockNumber, proof: Bytes) {
self.correct_set_by_number(block).1.report_malicious(validator, block, proof);
fn report_malicious(&self, validator: &Address, set_block: BlockNumber, block: BlockNumber, proof: Bytes) {
self.correct_set_by_number(set_block).1.report_malicious(validator, set_block, block, proof);
}
fn report_benign(&self, validator: &Address, block: BlockNumber) {
self.correct_set_by_number(block).1.report_benign(validator, block);
fn report_benign(&self, validator: &Address, set_block: BlockNumber, block: BlockNumber) {
self.correct_set_by_number(set_block).1.report_benign(validator, set_block, block);
}
fn register_contract(&self, client: Weak<Client>) {
@@ -153,18 +142,24 @@ impl ValidatorSet for Multi {
#[cfg(test)]
mod tests {
use util::*;
use types::ids::BlockId;
use spec::Spec;
use account_provider::AccountProvider;
use client::{BlockChainClient, EngineClient};
use engines::EpochChange;
use engines::validator_set::ValidatorSet;
use ethkey::Secret;
use header::Header;
use miner::MinerService;
use spec::Spec;
use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
use types::ids::BlockId;
use util::*;
use super::Multi;
#[test]
fn uses_current_set() {
::env_logger::init().unwrap();
let _ = ::env_logger::init();
let tap = Arc::new(AccountProvider::transient_provider());
let s0: Secret = "0".sha3().into();
let v0 = tap.insert_account(s0.clone(), "").unwrap();
@@ -205,4 +200,39 @@ mod tests {
sync_client.flush_queue();
assert_eq!(sync_client.chain_info().best_block_number, 3);
}
#[test]
fn transition_to_fixed_list_instant() {
use super::super::SimpleList;
let mut map: BTreeMap<_, Box<ValidatorSet>> = BTreeMap::new();
let list1: Vec<_> = (0..10).map(|_| Address::random()).collect();
let list2 = {
let mut list = list1.clone();
list.push(Address::random());
list
};
map.insert(0, Box::new(SimpleList::new(list1)));
map.insert(500, Box::new(SimpleList::new(list2)));
let multi = Multi::new(map);
let mut header = Header::new();
header.set_number(499);
match multi.signals_epoch_end(false, &header, None, None) {
EpochChange::No => {},
_ => panic!("Expected no epoch signal change."),
}
assert!(multi.is_epoch_end(false, &header).is_none());
header.set_number(500);
match multi.signals_epoch_end(false, &header, None, None) {
EpochChange::No => {},
_ => panic!("Expected no epoch signal change."),
}
assert!(multi.is_epoch_end(false, &header).is_some());
}
}

View File

@@ -22,21 +22,23 @@ use native_contracts::ValidatorSet as Provider;
use util::*;
use util::cache::MemoryLruCache;
use rlp::{UntrustedRlp, RlpStream};
use basic_types::LogBloom;
use client::{Client, BlockChainClient};
use engines::Call;
use engines::{Call, Engine};
use header::Header;
use ids::BlockId;
use log_entry::LogEntry;
use receipt::Receipt;
use super::ValidatorSet;
use super::{SystemCall, 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[])";
const EVENT_NAME: &'static [u8] = &*b"InitiateChange(bytes32,address[])";
lazy_static! {
static ref EVENT_NAME_HASH: H256 = EVENT_NAME.sha3();
@@ -50,14 +52,68 @@ pub struct ValidatorSafeContract {
client: RwLock<Option<Weak<Client>>>, // TODO [keorn]: remove
}
fn encode_proof(nonce: U256, validators: &[Address]) -> Bytes {
use rlp::RlpStream;
// first proof is just a state proof call of `getValidators` at header's state.
fn encode_first_proof(header: &Header, state_items: &[Vec<u8>]) -> Bytes {
let mut stream = RlpStream::new_list(2);
stream.append(&nonce).append_list(validators);
stream.append(header).begin_list(state_items.len());
for item in state_items {
stream.append(item);
}
stream.out()
}
fn decode_first_proof(rlp: &UntrustedRlp) -> Result<(Header, Vec<DBValue>), ::error::Error> {
let header = rlp.val_at(0)?;
let state_items = rlp.at(1)?.iter().map(|x| {
let mut val = DBValue::new();
val.append_slice(x.data()?);
Ok(val)
}).collect::<Result<_, ::error::Error>>()?;
Ok((header, state_items))
}
// inter-contract proofs are a header and receipts.
// checking will involve ensuring that the receipts match the header and
// extracting the validator set from the receipts.
fn encode_proof(header: &Header, receipts: &[Receipt]) -> Bytes {
let mut stream = RlpStream::new_list(2);
stream.append(header).append_list(receipts);
stream.drain().to_vec()
}
fn decode_proof(rlp: &UntrustedRlp) -> Result<(Header, Vec<Receipt>), ::error::Error> {
Ok((rlp.val_at(0)?, rlp.list_at(1)?))
}
// given a provider and caller, generate proof. this will just be a state proof
// of `getValidators`.
fn prove_initial(provider: &Provider, header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
use std::cell::RefCell;
let epoch_proof = RefCell::new(None);
let res = {
let caller = |a, d| {
let (result, proof) = caller(a, d)?;
*epoch_proof.borrow_mut() = Some(encode_first_proof(header, &proof));
Ok(result)
};
provider.get_validators(caller)
.wait()
};
res.map(|validators| {
let proof = epoch_proof.into_inner().expect("epoch_proof always set after call; qed");
trace!(target: "engine", "obtained proof for initial set: {} validators, {} bytes",
validators.len(), proof.len());
proof
})
}
impl ValidatorSafeContract {
pub fn new(contract_address: Address) -> Self {
ValidatorSafeContract {
@@ -70,6 +126,7 @@ impl ValidatorSafeContract {
/// Queries the state and gets the set of validators.
fn get_list(&self, caller: &Call) -> Option<SimpleList> {
let caller = move |a, d| caller(a, d).map(|x| x.0);
match self.provider.get_validators(caller).wait() {
Ok(new) => {
debug!(target: "engine", "Set of validators obtained: {:?}", new);
@@ -82,17 +139,6 @@ 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:
@@ -109,12 +155,70 @@ impl ValidatorSafeContract {
//
// The log data is an array of all new validator addresses.
fn expected_bloom(&self, header: &Header) -> LogBloom {
let topics = vec![*EVENT_NAME_HASH, *header.parent_hash()];
debug!(target: "engine", "Expected topics for header {}: {:?}",
header.hash(), topics);
LogEntry {
address: self.address,
topics: vec![*EVENT_NAME_HASH, *header.parent_hash()],
topics: topics,
data: Vec::new(), // irrelevant for bloom.
}.bloom()
}
// check receipts for log event. bloom should be `expected_bloom` for the
// header the receipts correspond to.
fn extract_from_event(&self, bloom: LogBloom, header: &Header, receipts: &[Receipt]) -> Option<SimpleList> {
let check_log = |log: &LogEntry| {
log.address == self.address &&
log.topics.len() == 2 &&
log.topics[0] == *EVENT_NAME_HASH &&
log.topics[1] == *header.parent_hash()
};
let event = Provider::contract(&self.provider)
.event("InitiateChange".into())
.expect("Contract known ahead of time to have `InitiateChange` event; qed");
// iterate in reverse because only the _last_ change in a given
// block actually has any effect.
// the contract should only increment the nonce once.
let mut decoded_events = receipts.iter()
.rev()
.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();
event.decode_log(topics, log.data.clone()).ok()
});
match decoded_events.next() {
None => None,
Some(matched_event) => {
// decode log manually until the native contract generator is
// good enough to do it for us.
let &(_, _, ref validators_token) = &matched_event.params[1];
let validators = validators_token.clone().to_array()
.and_then(|a| a.into_iter()
.map(|x| x.to_address().map(H160))
.collect::<Option<Vec<_>>>()
)
.map(SimpleList::new);
if validators.is_none() {
debug!(target: "engine", "Successfully decoded log turned out to be bad.");
}
trace!(target: "engine", "decoded log. validators: {:?}", validators);
validators
}
}
}
}
impl ValidatorSet for ValidatorSafeContract {
@@ -123,97 +227,144 @@ impl ValidatorSet for ValidatorSafeContract {
Box::new(move |addr, data| client.as_ref()
.and_then(Weak::upgrade)
.ok_or("No client!".into())
.and_then(|c| c.call_contract(id, addr, data)))
.and_then(|c| c.call_contract(id, addr, data))
.map(|out| (out, Vec::new()))) // generate no proofs in general
}
fn is_epoch_end(&self, header: &Header, _block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
fn on_epoch_begin(&self, first: bool, _header: &Header, caller: &mut SystemCall) -> Result<(), ::error::Error> {
if first { return Ok(()) } // only signalled changes need to be noted.
self.provider.finalize_change(caller)
.wait()
.map_err(::engines::EngineError::FailedSystemCall)
.map_err(Into::into)
}
fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result<Vec<u8>, String> {
prove_initial(&self.provider, header, call)
}
fn is_epoch_end(&self, _first: bool, _chain_head: &Header) -> Option<Vec<u8>> {
None // no immediate transitions to contract.
}
fn signals_epoch_end(&self, first: bool, header: &Header, _block: Option<&[u8]>, receipts: Option<&[Receipt]>)
-> ::engines::EpochChange
{
// transition to the first block of a contract requires finality but has no log event.
if first {
debug!(target: "engine", "signalling transition to fresh contract.");
let (provider, header) = (self.provider.clone(), header.clone());
let with_caller: Box<Fn(&Call) -> _> = Box::new(move |caller| prove_initial(&provider, &header, caller));
return ::engines::EpochChange::Yes(::engines::Proof::WithState(with_caller))
}
// otherwise, we're checking for logs.
let bloom = self.expected_bloom(header);
let header_bloom = header.log_bloom();
if &bloom & header_bloom != bloom { return ::engines::EpochChange::No }
trace!(target: "engine", "detected epoch change event bloom");
match receipts {
None => ::engines::EpochChange::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.
};
Some(receipts) => match self.extract_from_event(bloom, header, receipts) {
None => ::engines::EpochChange::No,
Some(_) => {
debug!(target: "engine", "signalling transition within contract");
let event = Provider::contract(&self.provider)
.event("ValidatorsChanged".into())
.expect("Contract known ahead of time to have `ValidatorsChanged` event; qed");
// iterate in reverse because only the _last_ change in a given
// block actually has any effect.
// the contract should only increment the nonce once.
let mut decoded_events = receipts.iter()
.rev()
.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,
}
});
match decoded_events.next() {
None => ::engines::EpochChange::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[1];
let &(_, _, ref validators_token) = &matched_event.params[2];
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(_)) => {
let new_epoch = nonce.low_u64();
::engines::EpochChange::Yes(new_epoch)
}
_ => {
debug!(target: "engine", "Successfully decoded log turned out to be bad.");
::engines::EpochChange::No
}
}
}
let proof = encode_proof(&header, receipts);
::engines::EpochChange::Yes(::engines::Proof::Known(proof))
}
}
},
}
}
// the proof we generate is an RLP list containing two parts.
// (nonce, validators)
fn epoch_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 epoch_set(&self, _header: &Header, proof: &[u8]) -> Result<(u64, SimpleList), ::error::Error> {
use rlp::UntrustedRlp;
fn epoch_set(&self, first: bool, engine: &Engine, _number: ::header::BlockNumber, proof: &[u8])
-> Result<(SimpleList, Option<H256>), ::error::Error>
{
use transaction::{Action, Transaction};
let rlp = UntrustedRlp::new(proof);
let nonce: u64 = rlp.val_at(0)?;
let validators: Vec<Address> = rlp.list_at(1)?;
Ok((nonce, SimpleList::new(validators)))
if first {
trace!(target: "engine", "Recovering initial epoch set");
// TODO: match client contract_call_tx more cleanly without duplication.
const PROVIDED_GAS: u64 = 50_000_000;
let (old_header, state_items) = decode_first_proof(&rlp)?;
let old_hash = old_header.hash();
let env_info = ::env_info::EnvInfo {
number: old_header.number(),
author: *old_header.author(),
difficulty: *old_header.difficulty(),
gas_limit: PROVIDED_GAS.into(),
timestamp: old_header.timestamp(),
last_hashes: {
// this will break if we don't inclue all 256 last hashes.
let mut last_hashes: Vec<_> = (0..256).map(|_| H256::default()).collect();
last_hashes[255] = *old_header.parent_hash();
Arc::new(last_hashes)
},
gas_used: 0.into(),
};
// check state proof using given engine.
let number = old_header.number();
let addresses = self.provider.get_validators(move |a, d| {
let from = Address::default();
let tx = Transaction {
nonce: engine.account_start_nonce(number),
action: Action::Call(a),
gas: PROVIDED_GAS.into(),
gas_price: U256::default(),
value: U256::default(),
data: d,
}.fake_sign(from);
let res = ::state::check_proof(
&state_items,
*old_header.state_root(),
&tx,
engine,
&env_info,
);
match res {
::state::ProvedExecution::BadProof => Err("Bad proof".into()),
::state::ProvedExecution::Failed(e) => Err(format!("Failed call: {}", e)),
::state::ProvedExecution::Complete(e) => Ok(e.output),
}
}).wait().map_err(::engines::EngineError::InsufficientProof)?;
trace!(target: "engine", "extracted epoch set at #{}: {} addresses",
number, addresses.len());
Ok((SimpleList::new(addresses), Some(old_hash)))
} else {
let (old_header, receipts) = decode_proof(&rlp)?;
// ensure receipts match header.
// TODO: optimize? these were just decoded.
let found_root = ::util::triehash::ordered_trie_root(
receipts.iter().map(::rlp::encode).map(|x| x.to_vec())
);
if found_root != *old_header.receipts_root() {
return Err(::error::BlockError::InvalidReceiptsRoot(
Mismatch { expected: *old_header.receipts_root(), found: found_root }
).into());
}
let bloom = self.expected_bloom(&old_header);
match self.extract_from_event(bloom, &old_header, &receipts) {
Some(list) => Ok((list, Some(old_header.hash()))),
None => Err(::engines::EngineError::InsufficientProof("No log event in proof.".into()).into()),
}
}
}
fn contains_with_caller(&self, block_hash: &H256, address: &Address, caller: &Call) -> bool {
@@ -371,6 +522,7 @@ mod tests {
let last_hash = client.best_block_header().hash();
let mut new_header = Header::default();
new_header.set_parent_hash(last_hash);
new_header.set_number(1); // so the validator set looks for a log.
// first, try without the parent hash.
let mut event = LogEntry {
@@ -380,12 +532,35 @@ mod tests {
};
new_header.set_log_bloom(event.bloom());
assert_eq!(engine.is_epoch_end(&new_header, None, None), EpochChange::No);
match engine.signals_epoch_end(&new_header, None, None) {
EpochChange::No => {},
_ => panic!("Expected bloom to be unrecognized."),
};
// with the last hash, it should need the receipts.
event.topics.push(last_hash);
new_header.set_log_bloom(event.bloom());
assert_eq!(engine.is_epoch_end(&new_header, None, None),
EpochChange::Unsure(Unsure::NeedsReceipts));
match engine.signals_epoch_end(&new_header, None, None) {
EpochChange::Unsure(Unsure::NeedsReceipts) => {},
_ => panic!("Expected bloom to be recognized."),
};
}
#[test]
fn initial_contract_is_signal() {
use header::Header;
use engines::{EpochChange, Proof};
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None);
let engine = client.engine().clone();
let mut new_header = Header::default();
new_header.set_number(0); // so the validator set doesn't look for a log
match engine.signals_epoch_end(&new_header, None, None) {
EpochChange::Yes(Proof::WithState(_)) => {},
_ => panic!("Expected state to be required to prove initial signal"),
};
}
}

View File

@@ -18,8 +18,8 @@
use util::{H256, Address, HeapSizeOf};
use engines::Call;
use header::Header;
use engines::{Call, Engine};
use header::{BlockNumber, Header};
use super::ValidatorSet;
/// Validator set containing a known set of addresses.
@@ -42,6 +42,20 @@ impl SimpleList {
}
}
impl ::std::ops::Deref for SimpleList {
type Target = [Address];
fn deref(&self) -> &[Address] { &self.validators }
}
impl From<Vec<Address>> for SimpleList {
fn from(validators: Vec<Address>) -> Self {
SimpleList {
validators: validators,
}
}
}
impl HeapSizeOf for SimpleList {
fn heap_size_of_children(&self) -> usize {
self.validators.heap_size_of_children()
@@ -53,18 +67,21 @@ impl ValidatorSet for SimpleList {
Box::new(|_, _| Err("Simple list doesn't require calls.".into()))
}
fn is_epoch_end(&self, _header: &Header, _block: Option<&[u8]>, _receipts: Option<&[::receipt::Receipt]>)
fn is_epoch_end(&self, first: bool, _chain_head: &Header) -> Option<Vec<u8>> {
match first {
true => Some(Vec::new()), // allow transition to fixed list, and instantly
false => None,
}
}
fn signals_epoch_end(&self, _: bool, _: &Header, _: Option<&[u8]>, _: Option<&[::receipt::Receipt]>)
-> ::engines::EpochChange
{
::engines::EpochChange::No
}
fn epoch_proof(&self, _header: &Header, _caller: &Call) -> Result<Vec<u8>, String> {
Ok(Vec::new())
}
fn epoch_set(&self, _header: &Header, _: &[u8]) -> Result<(u64, SimpleList), ::error::Error> {
Ok((0, self.clone()))
fn epoch_set(&self, _first: bool, _: &Engine, _: BlockNumber, _: &[u8]) -> Result<(SimpleList, Option<H256>), ::error::Error> {
Ok((self.clone(), None))
}
fn contains_with_caller(&self, _bh: &H256, address: &Address, _: &Call) -> bool {
@@ -73,6 +90,11 @@ impl ValidatorSet for SimpleList {
fn get_with_caller(&self, _bh: &H256, nonce: usize, _: &Call) -> Address {
let validator_n = self.validators.len();
if validator_n == 0 {
panic!("Cannot operate with an empty validator set.");
}
self.validators.get(nonce % validator_n).expect("There are validator_n authorities; taking number modulo validator_n gives number in validator_n range; qed").clone()
}

View File

@@ -20,7 +20,7 @@ use std::str::FromStr;
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
use util::{Arc, Bytes, H256, Address, HeapSizeOf};
use engines::Call;
use engines::{Call, Engine};
use header::{Header, BlockNumber};
use super::{ValidatorSet, SimpleList};
@@ -52,18 +52,16 @@ impl ValidatorSet for TestSet {
Box::new(|_, _| Err("Test set doesn't require calls.".into()))
}
fn is_epoch_end(&self, _header: &Header, _block: Option<&[u8]>, _receipts: Option<&[::receipt::Receipt]>)
fn is_epoch_end(&self, _first: bool, _chain_head: &Header) -> Option<Vec<u8>> { None }
fn signals_epoch_end(&self, _: bool, _: &Header, _: Option<&[u8]>, _: Option<&[::receipt::Receipt]>)
-> ::engines::EpochChange
{
::engines::EpochChange::No
}
fn epoch_proof(&self, _header: &Header, _caller: &Call) -> Result<Vec<u8>, String> {
Ok(Vec::new())
}
fn epoch_set(&self, _header: &Header, _: &[u8]) -> Result<(u64, SimpleList), ::error::Error> {
Ok((0, self.validator.clone()))
fn epoch_set(&self, _: bool, _: &Engine, _: BlockNumber, _: &[u8]) -> Result<(SimpleList, Option<H256>), ::error::Error> {
Ok((self.validator.clone(), None))
}
fn contains_with_caller(&self, bh: &H256, address: &Address, _: &Call) -> bool {
@@ -78,11 +76,11 @@ impl ValidatorSet for TestSet {
1
}
fn report_malicious(&self, _validator: &Address, block: BlockNumber, _proof: Bytes) {
fn report_malicious(&self, _validator: &Address, _set_block: BlockNumber, block: BlockNumber, _proof: Bytes) {
self.last_malicious.store(block as usize, AtomicOrdering::SeqCst)
}
fn report_benign(&self, _validator: &Address, block: BlockNumber) {
fn report_benign(&self, _validator: &Address, _set_block: BlockNumber, block: BlockNumber) {
self.last_benign.store(block as usize, AtomicOrdering::SeqCst)
}
}