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:
committed by
Arkadiy Paronyan
parent
3637c14da5
commit
d069b98b45
@@ -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),
|
||||
|
||||
@@ -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>) {}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user