Add POSDAO transition and malice report queue. (#11245)
* Add POSDAO transition; call emitInitiateChange. * Retry failed malice reports. * Make malice reports with zero gas price. * Address review comments. * Extract ReportQueue from ValidatorSafeContract. * Add shouldValidatorReport to validator set contract. * Rename queue_report to enqueue_report * Increment nonce between randomness and POSDAO transactions. * Refactor the test ValidatorSet contract * Address review comments, docs * Update ethcore/res/validator_contract.sol Co-Authored-By: David <dvdplm@gmail.com> * Update ethcore/res/validator_contract.sol Co-Authored-By: David <dvdplm@gmail.com> Co-authored-by: varasev <33550681+varasev@users.noreply.github.com> Co-authored-by: David <dvdplm@gmail.com>
This commit is contained in:
parent
ceda9d95de
commit
bf44f024c6
@ -444,9 +444,9 @@ impl TransactionRequest {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a gas price. If this is not specified, a sensible default is used.
|
/// Sets a gas price. If this is not specified or `None`, a sensible default is used.
|
||||||
pub fn gas_price(mut self, gas_price: U256) -> TransactionRequest {
|
pub fn gas_price<T: Into<Option<U256>>>(mut self, gas_price: T) -> TransactionRequest {
|
||||||
self.gas_price = Some(gas_price);
|
self.gas_price = gas_price.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ use itertools::{self, Itertools};
|
|||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use rlp::{encode, Decodable, DecoderError, Encodable, RlpStream, Rlp};
|
use rlp::{encode, Decodable, DecoderError, Encodable, RlpStream, Rlp};
|
||||||
use ethereum_types::{H256, H520, Address, U128, U256};
|
use ethereum_types::{H256, H520, Address, U128, U256};
|
||||||
|
use parity_bytes::Bytes;
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use time_utils::CheckedSystemTime;
|
use time_utils::CheckedSystemTime;
|
||||||
use common_types::{
|
use common_types::{
|
||||||
@ -80,7 +81,7 @@ use common_types::{
|
|||||||
transaction::SignedTransaction,
|
transaction::SignedTransaction,
|
||||||
};
|
};
|
||||||
use unexpected::{Mismatch, OutOfBounds};
|
use unexpected::{Mismatch, OutOfBounds};
|
||||||
use validator_set::{ValidatorSet, SimpleList, new_validator_set};
|
use validator_set::{ValidatorSet, SimpleList, new_validator_set_posdao};
|
||||||
|
|
||||||
mod finality;
|
mod finality;
|
||||||
mod randomness;
|
mod randomness;
|
||||||
@ -128,6 +129,9 @@ pub struct AuthorityRoundParams {
|
|||||||
/// The addresses of contracts that determine the block gas limit with their associated block
|
/// The addresses of contracts that determine the block gas limit with their associated block
|
||||||
/// numbers.
|
/// numbers.
|
||||||
pub block_gas_limit_contract_transitions: BTreeMap<u64, Address>,
|
pub block_gas_limit_contract_transitions: BTreeMap<u64, Address>,
|
||||||
|
/// If set, this is the block number at which the consensus engine switches from AuRa to AuRa
|
||||||
|
/// with POSDAO modifications.
|
||||||
|
pub posdao_transition: Option<BlockNumber>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const U16_MAX: usize = ::std::u16::MAX as usize;
|
const U16_MAX: usize = ::std::u16::MAX as usize;
|
||||||
@ -195,7 +199,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
|
|||||||
.collect();
|
.collect();
|
||||||
AuthorityRoundParams {
|
AuthorityRoundParams {
|
||||||
step_durations,
|
step_durations,
|
||||||
validators: new_validator_set(p.validators),
|
validators: new_validator_set_posdao(p.validators, p.posdao_transition.map(Into::into)),
|
||||||
start_step: p.start_step.map(Into::into),
|
start_step: p.start_step.map(Into::into),
|
||||||
validate_score_transition: p.validate_score_transition.map_or(0, Into::into),
|
validate_score_transition: p.validate_score_transition.map_or(0, Into::into),
|
||||||
validate_step_transition: p.validate_step_transition.map_or(0, Into::into),
|
validate_step_transition: p.validate_step_transition.map_or(0, Into::into),
|
||||||
@ -210,6 +214,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
|
|||||||
strict_empty_steps_transition: p.strict_empty_steps_transition.map_or(0, Into::into),
|
strict_empty_steps_transition: p.strict_empty_steps_transition.map_or(0, Into::into),
|
||||||
randomness_contract_address,
|
randomness_contract_address,
|
||||||
block_gas_limit_contract_transitions,
|
block_gas_limit_contract_transitions,
|
||||||
|
posdao_transition: p.posdao_transition.map(Into::into),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -598,6 +603,10 @@ pub struct AuthorityRound {
|
|||||||
block_gas_limit_contract_transitions: BTreeMap<u64, Address>,
|
block_gas_limit_contract_transitions: BTreeMap<u64, Address>,
|
||||||
/// Memoized gas limit overrides, by block hash.
|
/// Memoized gas limit overrides, by block hash.
|
||||||
gas_limit_override_cache: Mutex<LruCache<H256, Option<U256>>>,
|
gas_limit_override_cache: Mutex<LruCache<H256, Option<U256>>>,
|
||||||
|
/// The block number at which the consensus engine switches from AuRa to AuRa with POSDAO
|
||||||
|
/// modifications. For details about POSDAO, see the whitepaper:
|
||||||
|
/// https://www.xdaichain.com/for-validators/posdao-whitepaper
|
||||||
|
posdao_transition: Option<BlockNumber>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// header-chain validator.
|
// header-chain validator.
|
||||||
@ -893,6 +902,7 @@ impl AuthorityRound {
|
|||||||
randomness_contract_address: our_params.randomness_contract_address,
|
randomness_contract_address: our_params.randomness_contract_address,
|
||||||
block_gas_limit_contract_transitions: our_params.block_gas_limit_contract_transitions,
|
block_gas_limit_contract_transitions: our_params.block_gas_limit_contract_transitions,
|
||||||
gas_limit_override_cache: Mutex::new(LruCache::new(GAS_LIMIT_OVERRIDE_CACHE_CAPACITY)),
|
gas_limit_override_cache: Mutex::new(LruCache::new(GAS_LIMIT_OVERRIDE_CACHE_CAPACITY)),
|
||||||
|
posdao_transition: our_params.posdao_transition,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Do not initialize timeouts for tests.
|
// Do not initialize timeouts for tests.
|
||||||
@ -1131,6 +1141,53 @@ impl AuthorityRound {
|
|||||||
EngineError::RequiresClient
|
EngineError::RequiresClient
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_posdao(&self, block: &ExecutedBlock, nonce: Option<U256>) -> Result<Vec<SignedTransaction>, Error> {
|
||||||
|
// Skip the rest of the function unless there has been a transition to POSDAO AuRa.
|
||||||
|
if self.posdao_transition.map_or(true, |posdao_block| block.header.number() < posdao_block) {
|
||||||
|
trace!(target: "engine", "Skipping POSDAO calls to validator set contracts");
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let opt_signer = self.signer.read();
|
||||||
|
let signer = match opt_signer.as_ref() {
|
||||||
|
Some(signer) => signer,
|
||||||
|
None => return Ok(Vec::new()), // We are not a validator, so we shouldn't call the contracts.
|
||||||
|
};
|
||||||
|
let our_addr = signer.address();
|
||||||
|
let client = self.upgrade_client_or("Unable to prepare block")?;
|
||||||
|
let full_client = client.as_full_client().ok_or_else(|| {
|
||||||
|
EngineError::FailedSystemCall("Failed to upgrade to BlockchainClient.".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Makes a constant contract call.
|
||||||
|
let mut call = |to: Address, data: Bytes| {
|
||||||
|
full_client.call_contract(BlockId::Latest, to, data).map_err(|e| format!("{}", e))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Our current account nonce. The transactions must have consecutive nonces, starting with this one.
|
||||||
|
let mut tx_nonce = if let Some(tx_nonce) = nonce {
|
||||||
|
tx_nonce
|
||||||
|
} else {
|
||||||
|
block.state.nonce(&our_addr)?
|
||||||
|
};
|
||||||
|
let mut transactions = Vec::new();
|
||||||
|
|
||||||
|
// Creates and signs a transaction with the given contract call.
|
||||||
|
let mut make_transaction = |to: Address, data: Bytes| -> Result<SignedTransaction, Error> {
|
||||||
|
let tx_request = TransactionRequest::call(to, data).gas_price(U256::zero()).nonce(tx_nonce);
|
||||||
|
tx_nonce += U256::one(); // Increment the nonce for the next transaction.
|
||||||
|
Ok(full_client.create_transaction(tx_request)?)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Genesis is never a new block, but might as well check.
|
||||||
|
let first = block.header.number() == 0;
|
||||||
|
for (addr, data) in self.validators.generate_engine_transactions(first, &block.header, &mut call)? {
|
||||||
|
transactions.push(make_transaction(addr, data)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(transactions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unix_now() -> Duration {
|
fn unix_now() -> Duration {
|
||||||
@ -1519,7 +1576,10 @@ impl Engine for AuthorityRound {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn generate_engine_transactions(&self, block: &ExecutedBlock) -> Result<Vec<SignedTransaction>, Error> {
|
fn generate_engine_transactions(&self, block: &ExecutedBlock) -> Result<Vec<SignedTransaction>, Error> {
|
||||||
self.run_randomness_phase(block)
|
let mut transactions = self.run_randomness_phase(block)?;
|
||||||
|
let nonce = transactions.last().map(|tx| tx.nonce + U256::one());
|
||||||
|
transactions.extend(self.run_posdao(block, nonce)?);
|
||||||
|
Ok(transactions)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check the number of seal fields.
|
/// Check the number of seal fields.
|
||||||
@ -1976,6 +2036,7 @@ mod tests {
|
|||||||
two_thirds_majority_transition: 0,
|
two_thirds_majority_transition: 0,
|
||||||
randomness_contract_address: BTreeMap::new(),
|
randomness_contract_address: BTreeMap::new(),
|
||||||
block_gas_limit_contract_transitions: BTreeMap::new(),
|
block_gas_limit_contract_transitions: BTreeMap::new(),
|
||||||
|
posdao_transition: Some(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
// mutate aura params
|
// mutate aura params
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
[
|
[
|
||||||
{"constant":false,"inputs":[{"name":"validator","type":"address"},{"name":"blockNumber","type":"uint256"},{"name":"proof","type":"bytes"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"},
|
{"constant":false,"inputs":[{"name":"validator","type":"address"},{"name":"blockNumber","type":"uint256"},{"name":"proof","type":"bytes"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"},
|
||||||
{"constant":false,"inputs":[{"name":"validator","type":"address"},{"name":"blockNumber","type":"uint256"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"}
|
{"constant":false,"inputs":[{"name":"validator","type":"address"},{"name":"blockNumber","type":"uint256"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"},
|
||||||
|
{"constant": true, "inputs": [ { "name": "validator", "type": "address" }, { "name": "blockNum", "type": "uint256" } ], "name": "maliceReportedForBlock", "outputs": [ { "name": "", "type": "address[]" } ], "payable": false, "stateMutability": "view", "type": "function" }
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,55 @@
|
|||||||
[
|
[
|
||||||
{"constant":false,"inputs":[],"name":"finalizeChange","outputs":[],"payable":false,"type":"function"},
|
{"constant":false,"inputs":[],"name":"finalizeChange","outputs":[],"payable":false,"type":"function"},
|
||||||
{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"validators","type":"address[]"}],"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":false,"name":"_new_set","type":"address[]"}],"name":"InitiateChange","type":"event"}
|
{"anonymous":false,"inputs":[{"indexed":true,"name":"_parent_hash","type":"bytes32"},{"indexed":false,"name":"_new_set","type":"address[]"}],"name":"InitiateChange","type":"event"},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "emitInitiateChangeCallable",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "emitInitiateChange",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_reportingValidator",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_maliciousValidator",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_blockNumber",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "shouldValidatorReport",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
@ -21,8 +21,8 @@ use std::sync::Weak;
|
|||||||
|
|
||||||
use parity_bytes::Bytes;
|
use parity_bytes::Bytes;
|
||||||
use ethabi_contract::use_contract;
|
use ethabi_contract::use_contract;
|
||||||
use ethereum_types::{H256, Address};
|
use ethereum_types::{H256, U256, Address};
|
||||||
use log::{warn, trace};
|
use log::{info, warn, trace};
|
||||||
use machine::Machine;
|
use machine::Machine;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use common_types::{
|
use common_types::{
|
||||||
@ -31,6 +31,7 @@ use common_types::{
|
|||||||
header::Header,
|
header::Header,
|
||||||
errors::EthcoreError,
|
errors::EthcoreError,
|
||||||
engines::machine::{Call, AuxiliaryData},
|
engines::machine::{Call, AuxiliaryData},
|
||||||
|
transaction,
|
||||||
};
|
};
|
||||||
|
|
||||||
use client_traits::{EngineClient, TransactionRequest};
|
use client_traits::{EngineClient, TransactionRequest};
|
||||||
@ -48,31 +49,63 @@ pub struct ValidatorContract {
|
|||||||
contract_address: Address,
|
contract_address: Address,
|
||||||
validators: ValidatorSafeContract,
|
validators: ValidatorSafeContract,
|
||||||
client: RwLock<Option<Weak<dyn EngineClient>>>, // TODO [keorn]: remove
|
client: RwLock<Option<Weak<dyn EngineClient>>>, // TODO [keorn]: remove
|
||||||
|
posdao_transition: Option<BlockNumber>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValidatorContract {
|
impl ValidatorContract {
|
||||||
pub fn new(contract_address: Address) -> Self {
|
pub fn new(contract_address: Address, posdao_transition: Option<BlockNumber>) -> Self {
|
||||||
ValidatorContract {
|
ValidatorContract {
|
||||||
contract_address,
|
contract_address,
|
||||||
validators: ValidatorSafeContract::new(contract_address),
|
validators: ValidatorSafeContract::new(contract_address, posdao_transition),
|
||||||
client: RwLock::new(None),
|
client: RwLock::new(None),
|
||||||
|
posdao_transition,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValidatorContract {
|
impl ValidatorContract {
|
||||||
fn transact(&self, data: Bytes) -> Result<(), String> {
|
fn transact(&self, data: Bytes, gas_price: Option<U256>, client: &dyn EngineClient) -> Result<(), String> {
|
||||||
let client = self.client.read().as_ref()
|
let full_client = client.as_full_client().ok_or("No full client!")?;
|
||||||
.and_then(Weak::upgrade)
|
let tx_request = TransactionRequest::call(self.contract_address, data).gas_price(gas_price);
|
||||||
.ok_or_else(|| "No client!")?;
|
match full_client.transact(tx_request) {
|
||||||
|
Ok(()) | Err(transaction::Error::AlreadyImported) => Ok(()),
|
||||||
|
Err(e) => Err(e.to_string())?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match client.as_full_client() {
|
fn do_report_malicious(&self, address: &Address, block: BlockNumber, proof: Bytes) -> Result<(), EthcoreError> {
|
||||||
Some(c) => {
|
let client = self.client.read().as_ref().and_then(Weak::upgrade).ok_or("No client!")?;
|
||||||
c.transact(TransactionRequest::call(self.contract_address, data))
|
let latest = client.block_header(BlockId::Latest).ok_or("No latest block!")?;
|
||||||
.map_err(|e| format!("Transaction import error: {}", e))?;
|
if !self.contains(&latest.parent_hash(), address) {
|
||||||
Ok(())
|
warn!(target: "engine", "Not reporting {} on block {}: Not a validator", address, block);
|
||||||
},
|
return Ok(());
|
||||||
None => Err("No full client!".into()),
|
}
|
||||||
|
let data = validator_report::functions::report_malicious::encode_input(*address, block, proof);
|
||||||
|
self.validators.enqueue_report(*address, block, data.clone());
|
||||||
|
let gas_price = self.report_gas_price(latest.number());
|
||||||
|
self.transact(data, gas_price, &*client)?;
|
||||||
|
warn!(target: "engine", "Reported malicious validator {} at block {}", address, block);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_report_benign(&self, address: &Address, block: BlockNumber) -> Result<(), EthcoreError> {
|
||||||
|
let client = self.client.read().as_ref().and_then(Weak::upgrade).ok_or("No client!")?;
|
||||||
|
let latest = client.block_header(BlockId::Latest).ok_or("No latest block!")?;
|
||||||
|
let data = validator_report::functions::report_benign::encode_input(*address, block);
|
||||||
|
let gas_price = self.report_gas_price(latest.number());
|
||||||
|
self.transact(data, gas_price, &*client)?;
|
||||||
|
warn!(target: "engine", "Benign report for validator {} at block {}", address, block);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the gas price for report transactions.
|
||||||
|
///
|
||||||
|
/// After `posdaoTransition`, this is zero. Otherwise it is the default (`None`).
|
||||||
|
fn report_gas_price(&self, block: BlockNumber) -> Option<U256> {
|
||||||
|
if self.posdao_transition? <= block {
|
||||||
|
Some(0.into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,6 +115,16 @@ impl ValidatorSet for ValidatorContract {
|
|||||||
self.validators.default_caller(id)
|
self.validators.default_caller(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_engine_transactions(&self, first: bool, header: &Header, call: &mut SystemCall)
|
||||||
|
-> Result<Vec<(Address, Bytes)>, EthcoreError>
|
||||||
|
{
|
||||||
|
self.validators.generate_engine_transactions(first, header, call)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_close_block(&self, header: &Header, address: &Address) -> Result<(), EthcoreError> {
|
||||||
|
self.validators.on_close_block(header, address)
|
||||||
|
}
|
||||||
|
|
||||||
fn on_epoch_begin(&self, first: bool, header: &Header, call: &mut SystemCall) -> Result<(), EthcoreError> {
|
fn on_epoch_begin(&self, first: bool, header: &Header, call: &mut SystemCall) -> Result<(), EthcoreError> {
|
||||||
self.validators.on_epoch_begin(first, header, call)
|
self.validators.on_epoch_begin(first, header, call)
|
||||||
}
|
}
|
||||||
@ -120,19 +163,15 @@ impl ValidatorSet for ValidatorContract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn report_malicious(&self, address: &Address, _set_block: BlockNumber, block: BlockNumber, proof: Bytes) {
|
fn report_malicious(&self, address: &Address, _set_block: BlockNumber, block: BlockNumber, proof: Bytes) {
|
||||||
let data = validator_report::functions::report_malicious::encode_input(*address, block, proof);
|
if let Err(s) = self.do_report_malicious(address, block, proof) {
|
||||||
match self.transact(data) {
|
warn!(target: "engine", "Validator {} could not be reported ({}) on block {}", address, s, block);
|
||||||
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, _set_block: BlockNumber, block: BlockNumber) {
|
fn report_benign(&self, address: &Address, _set_block: BlockNumber, block: BlockNumber) {
|
||||||
trace!(target: "engine", "validator set recording benign misbehaviour at block #{} by {:#x}", block, address);
|
trace!(target: "engine", "validator set recording benign misbehaviour at block #{} by {:#x}", block, address);
|
||||||
let data = validator_report::functions::report_benign::encode_input(*address, block);
|
if let Err(s) = self.do_report_benign(address, block) {
|
||||||
match self.transact(data) {
|
warn!(target: "engine", "Validator {} could not be reported ({}) on block {}", address, s, block);
|
||||||
Ok(_) => warn!(target: "engine", "Reported benign validator misbehaviour {}", address),
|
|
||||||
Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,6 +189,7 @@ mod tests {
|
|||||||
use call_contract::CallContract;
|
use call_contract::CallContract;
|
||||||
use common_types::{header::Header, ids::BlockId};
|
use common_types::{header::Header, ids::BlockId};
|
||||||
use client_traits::{BlockChainClient, ChainInfo, BlockInfo, TransactionRequest};
|
use client_traits::{BlockChainClient, ChainInfo, BlockInfo, TransactionRequest};
|
||||||
|
use ethabi::FunctionOutputDecoder;
|
||||||
use ethcore::{
|
use ethcore::{
|
||||||
miner::{self, MinerService},
|
miner::{self, MinerService},
|
||||||
test_helpers::generate_dummy_client_with_spec,
|
test_helpers::generate_dummy_client_with_spec,
|
||||||
@ -167,7 +207,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn fetches_validators() {
|
fn fetches_validators() {
|
||||||
let client = generate_dummy_client_with_spec(spec::new_validator_contract);
|
let client = generate_dummy_client_with_spec(spec::new_validator_contract);
|
||||||
let vc = Arc::new(ValidatorContract::new("0000000000000000000000000000000000000005".parse::<Address>().unwrap()));
|
let addr: Address = "0000000000000000000000000000000000000005".parse().unwrap();
|
||||||
|
let vc = Arc::new(ValidatorContract::new(addr, None));
|
||||||
vc.register_client(Arc::downgrade(&client) as _);
|
vc.register_client(Arc::downgrade(&client) as _);
|
||||||
let last_hash = client.best_block_header().hash();
|
let last_hash = client.best_block_header().hash();
|
||||||
assert!(vc.contains(&last_hash, &"7d577a597b2742b498cb5cf0c26cdcd726d39e6e".parse::<Address>().unwrap()));
|
assert!(vc.contains(&last_hash, &"7d577a597b2742b498cb5cf0c26cdcd726d39e6e".parse::<Address>().unwrap()));
|
||||||
@ -198,6 +239,8 @@ mod tests {
|
|||||||
assert!(client.engine().verify_block_external(&header).is_err());
|
assert!(client.engine().verify_block_external(&header).is_err());
|
||||||
client.engine().step();
|
client.engine().step();
|
||||||
assert_eq!(client.chain_info().best_block_number, 0);
|
assert_eq!(client.chain_info().best_block_number, 0);
|
||||||
|
// `reportBenign` when the designated proposer releases block from the future (bad clock).
|
||||||
|
assert!(client.engine().verify_block_basic(&header).is_err());
|
||||||
|
|
||||||
// Now create one that is more in future. That one should be rejected and validator should be reported.
|
// Now create one that is more in future. That one should be rejected and validator should be reported.
|
||||||
let mut header = Header::default();
|
let mut header = Header::default();
|
||||||
@ -211,7 +254,7 @@ mod tests {
|
|||||||
// Seal a block.
|
// Seal a block.
|
||||||
client.engine().step();
|
client.engine().step();
|
||||||
assert_eq!(client.chain_info().best_block_number, 1);
|
assert_eq!(client.chain_info().best_block_number, 1);
|
||||||
// Check if the unresponsive validator is `disliked`.
|
// Check if the unresponsive validator is `disliked`. "d8f2e0bf" accesses the field `disliked`..
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client.call_contract(BlockId::Latest, validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(),
|
client.call_contract(BlockId::Latest, validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(),
|
||||||
"0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e"
|
"0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e"
|
||||||
@ -223,6 +266,9 @@ mod tests {
|
|||||||
client.engine().step();
|
client.engine().step();
|
||||||
client.engine().step();
|
client.engine().step();
|
||||||
assert_eq!(client.chain_info().best_block_number, 2);
|
assert_eq!(client.chain_info().best_block_number, 2);
|
||||||
|
let (data, decoder) = super::validator_report::functions::malice_reported_for_block::call(v1, 1);
|
||||||
|
let reported_enc = client.call_contract(BlockId::Latest, validator_contract, data).expect("call failed");
|
||||||
|
assert_ne!(Vec::<Address>::new(), decoder.decode(&reported_enc).expect("decoding failed"));
|
||||||
|
|
||||||
// Check if misbehaving validator was removed.
|
// Check if misbehaving validator was removed.
|
||||||
client.transact(TransactionRequest::call(Default::default(), Default::default())).unwrap();
|
client.transact(TransactionRequest::call(Default::default(), Default::default())).unwrap();
|
||||||
|
@ -49,18 +49,35 @@ use self::contract::ValidatorContract;
|
|||||||
use self::safe_contract::ValidatorSafeContract;
|
use self::safe_contract::ValidatorSafeContract;
|
||||||
use self::multi::Multi;
|
use self::multi::Multi;
|
||||||
|
|
||||||
/// Creates a validator set from spec.
|
/// Creates a validator set from the given spec and initializes a transition to POSDAO AuRa consensus.
|
||||||
pub fn new_validator_set(spec: ValidatorSpec) -> Box<dyn ValidatorSet> {
|
pub fn new_validator_set_posdao(
|
||||||
|
spec: ValidatorSpec,
|
||||||
|
posdao_transition: Option<BlockNumber>
|
||||||
|
) -> Box<dyn ValidatorSet> {
|
||||||
match spec {
|
match spec {
|
||||||
ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())),
|
ValidatorSpec::List(list) =>
|
||||||
ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new(address.into())),
|
Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())),
|
||||||
ValidatorSpec::Contract(address) => Box::new(ValidatorContract::new(address.into())),
|
ValidatorSpec::SafeContract(address) =>
|
||||||
ValidatorSpec::Multi(sequence) => Box::new(
|
Box::new(ValidatorSafeContract::new(address.into(), posdao_transition)),
|
||||||
Multi::new(sequence.into_iter().map(|(block, set)| (block.into(), new_validator_set(set))).collect())
|
ValidatorSpec::Contract(address) =>
|
||||||
),
|
Box::new(ValidatorContract::new(address.into(), posdao_transition)),
|
||||||
|
ValidatorSpec::Multi(sequence) => Box::new(Multi::new(
|
||||||
|
sequence
|
||||||
|
.into_iter()
|
||||||
|
.map(|(block, set)| (
|
||||||
|
block.into(),
|
||||||
|
new_validator_set_posdao(set, posdao_transition)
|
||||||
|
))
|
||||||
|
.collect()
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a validator set from the given spec.
|
||||||
|
pub fn new_validator_set(spec: ValidatorSpec) -> Box<dyn ValidatorSet> {
|
||||||
|
new_validator_set_posdao(spec, None)
|
||||||
|
}
|
||||||
|
|
||||||
/// A validator set.
|
/// A validator set.
|
||||||
pub trait ValidatorSet: Send + Sync + 'static {
|
pub trait ValidatorSet: Send + Sync + 'static {
|
||||||
/// Get the default "Call" helper, for use in general operation.
|
/// Get the default "Call" helper, for use in general operation.
|
||||||
@ -68,6 +85,17 @@ pub trait ValidatorSet: Send + Sync + 'static {
|
|||||||
// a strict dependency on state always being available.
|
// a strict dependency on state always being available.
|
||||||
fn default_caller(&self, block_id: BlockId) -> Box<Call>;
|
fn default_caller(&self, block_id: BlockId) -> Box<Call>;
|
||||||
|
|
||||||
|
/// Called for each new block this node is creating. If this block is
|
||||||
|
/// the first block of an epoch, this is called *after* `on_epoch_begin()`,
|
||||||
|
/// but with the same parameters.
|
||||||
|
///
|
||||||
|
/// Returns a list of contract calls to be pushed onto the new block.
|
||||||
|
fn generate_engine_transactions(&self, _first: bool, _header: &Header, _call: &mut SystemCall)
|
||||||
|
-> Result<Vec<(Address, Bytes)>, EthcoreError>;
|
||||||
|
|
||||||
|
/// Called on the close of every block.
|
||||||
|
fn on_close_block(&self, _header: &Header, _address: &Address) -> Result<(), EthcoreError>;
|
||||||
|
|
||||||
/// Checks if a given address is a validator,
|
/// Checks if a given address is a validator,
|
||||||
/// using underlying, default call mechanism.
|
/// using underlying, default call mechanism.
|
||||||
fn contains(&self, parent: &H256, address: &Address) -> bool {
|
fn contains(&self, parent: &H256, address: &Address) -> bool {
|
||||||
|
@ -51,6 +51,14 @@ impl Multi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_children<T, F>(&self, header: &Header, mut func: F) -> Result<T, EthcoreError>
|
||||||
|
where F: FnMut(&dyn ValidatorSet, bool) -> Result<T, EthcoreError>
|
||||||
|
{
|
||||||
|
let (set_block, set) = self.correct_set_by_number(header.number());
|
||||||
|
let first = set_block == header.number();
|
||||||
|
func(set, first)
|
||||||
|
}
|
||||||
|
|
||||||
fn correct_set(&self, id: BlockId) -> Option<&dyn ValidatorSet> {
|
fn correct_set(&self, id: BlockId) -> Option<&dyn ValidatorSet> {
|
||||||
match self.block_number.read()(id).map(|parent_block| self.correct_set_by_number(parent_block)) {
|
match self.block_number.read()(id).map(|parent_block| self.correct_set_by_number(parent_block)) {
|
||||||
Ok((_, set)) => Some(set),
|
Ok((_, set)) => Some(set),
|
||||||
@ -82,11 +90,20 @@ impl ValidatorSet for Multi {
|
|||||||
.unwrap_or_else(|| Box::new(|_, _| Err("No validator set for given ID.".into())))
|
.unwrap_or_else(|| Box::new(|_, _| Err("No validator set for given ID.".into())))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_epoch_begin(&self, _first: bool, header: &Header, call: &mut SystemCall) -> Result<(), EthcoreError> {
|
fn generate_engine_transactions(&self, _first: bool, header: &Header, call: &mut SystemCall)
|
||||||
let (set_block, set) = self.correct_set_by_number(header.number());
|
-> Result<Vec<(Address, Bytes)>, EthcoreError>
|
||||||
let first = set_block == header.number();
|
{
|
||||||
|
self.map_children(header, &mut |set: &dyn ValidatorSet, first| {
|
||||||
|
set.generate_engine_transactions(first, header, call)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
set.on_epoch_begin(first, header, call)
|
fn on_close_block(&self, header: &Header, address: &Address) -> Result<(), EthcoreError> {
|
||||||
|
self.map_children(header, &mut |set: &dyn ValidatorSet, _first| set.on_close_block(header, address))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_epoch_begin(&self, _first: bool, header: &Header, call: &mut SystemCall) -> Result<(), EthcoreError> {
|
||||||
|
self.map_children(header, &mut |set: &dyn ValidatorSet, first| set.on_epoch_begin(first, header, call))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result<Vec<u8>, String> {
|
fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result<Vec<u8>, String> {
|
||||||
|
@ -16,9 +16,10 @@
|
|||||||
|
|
||||||
/// Validator set maintained in a contract, updated using `getValidators` method.
|
/// Validator set maintained in a contract, updated using `getValidators` method.
|
||||||
|
|
||||||
use std::sync::{Weak, Arc};
|
use std::collections::VecDeque;
|
||||||
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
use client_traits::EngineClient;
|
use client_traits::{BlockChainClient, EngineClient, TransactionRequest};
|
||||||
use common_types::{
|
use common_types::{
|
||||||
BlockNumber,
|
BlockNumber,
|
||||||
header::Header,
|
header::Header,
|
||||||
@ -27,6 +28,7 @@ use common_types::{
|
|||||||
log_entry::LogEntry,
|
log_entry::LogEntry,
|
||||||
engines::machine::{Call, AuxiliaryData, AuxiliaryRequest},
|
engines::machine::{Call, AuxiliaryData, AuxiliaryRequest},
|
||||||
receipt::Receipt,
|
receipt::Receipt,
|
||||||
|
transaction::{self, Action, Transaction},
|
||||||
};
|
};
|
||||||
use ethabi::FunctionOutputDecoder;
|
use ethabi::FunctionOutputDecoder;
|
||||||
use ethabi_contract::use_contract;
|
use ethabi_contract::use_contract;
|
||||||
@ -34,11 +36,11 @@ use ethereum_types::{H256, U256, Address, Bloom};
|
|||||||
use keccak_hash::keccak;
|
use keccak_hash::keccak;
|
||||||
use kvdb::DBValue;
|
use kvdb::DBValue;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use log::{debug, info, trace};
|
use log::{debug, info, trace, warn};
|
||||||
use machine::Machine;
|
use machine::Machine;
|
||||||
use memory_cache::MemoryLruCache;
|
use memory_cache::MemoryLruCache;
|
||||||
use parity_bytes::Bytes;
|
use parity_bytes::Bytes;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::{Mutex, RwLock};
|
||||||
use rlp::{Rlp, RlpStream};
|
use rlp::{Rlp, RlpStream};
|
||||||
use unexpected::Mismatch;
|
use unexpected::Mismatch;
|
||||||
|
|
||||||
@ -47,6 +49,13 @@ use super::simple_list::SimpleList;
|
|||||||
|
|
||||||
use_contract!(validator_set, "res/validator_set.json");
|
use_contract!(validator_set, "res/validator_set.json");
|
||||||
|
|
||||||
|
/// The maximum number of reports to keep queued.
|
||||||
|
const MAX_QUEUED_REPORTS: usize = 10;
|
||||||
|
/// The maximum number of malice reports to include when creating a new block.
|
||||||
|
const MAX_REPORTS_PER_BLOCK: usize = 10;
|
||||||
|
/// Don't re-send malice reports every block. Skip this many before retrying.
|
||||||
|
const REPORTS_SKIP_BLOCKS: u64 = 1;
|
||||||
|
|
||||||
const MEMOIZE_CAPACITY: usize = 500;
|
const MEMOIZE_CAPACITY: usize = 500;
|
||||||
|
|
||||||
// TODO: ethabi should be able to generate this.
|
// TODO: ethabi should be able to generate this.
|
||||||
@ -86,6 +95,12 @@ pub struct ValidatorSafeContract {
|
|||||||
/// is the validator set valid for the blocks following that hash.
|
/// is the validator set valid for the blocks following that hash.
|
||||||
validators: RwLock<MemoryLruCache<H256, SimpleList>>,
|
validators: RwLock<MemoryLruCache<H256, SimpleList>>,
|
||||||
client: RwLock<Option<Weak<dyn EngineClient>>>, // TODO [keorn]: remove
|
client: RwLock<Option<Weak<dyn EngineClient>>>, // TODO [keorn]: remove
|
||||||
|
report_queue: Mutex<ReportQueue>,
|
||||||
|
/// The block number where we resent the queued reports last time.
|
||||||
|
resent_reports_in_block: Mutex<BlockNumber>,
|
||||||
|
/// If set, this is the block number at which the consensus engine switches from AuRa to AuRa
|
||||||
|
/// with POSDAO modifications.
|
||||||
|
posdao_transition: Option<BlockNumber>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// first proof is just a state proof call of `getValidators` at header's state.
|
// first proof is just a state proof call of `getValidators` at header's state.
|
||||||
@ -103,8 +118,6 @@ fn encode_first_proof(header: &Header, state_items: &[Vec<u8>]) -> Bytes {
|
|||||||
fn check_first_proof(machine: &Machine, contract_address: Address, old_header: Header, state_items: &[DBValue])
|
fn check_first_proof(machine: &Machine, contract_address: Address, old_header: Header, state_items: &[DBValue])
|
||||||
-> Result<Vec<Address>, String>
|
-> Result<Vec<Address>, String>
|
||||||
{
|
{
|
||||||
use common_types::transaction::{Action, Transaction};
|
|
||||||
|
|
||||||
// TODO: match client contract_call_tx more cleanly without duplication.
|
// TODO: match client contract_call_tx more cleanly without duplication.
|
||||||
const PROVIDED_GAS: u64 = 50_000_000;
|
const PROVIDED_GAS: u64 = 50_000_000;
|
||||||
|
|
||||||
@ -200,14 +213,44 @@ fn prove_initial(contract_address: Address, header: &Header, caller: &Call) -> R
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ValidatorSafeContract {
|
impl ValidatorSafeContract {
|
||||||
pub fn new(contract_address: Address) -> Self {
|
pub fn new(contract_address: Address, posdao_transition: Option<BlockNumber>) -> Self {
|
||||||
ValidatorSafeContract {
|
ValidatorSafeContract {
|
||||||
contract_address,
|
contract_address,
|
||||||
validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)),
|
validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)),
|
||||||
client: RwLock::new(None),
|
client: RwLock::new(None),
|
||||||
|
report_queue: Mutex::new(ReportQueue::default()),
|
||||||
|
resent_reports_in_block: Mutex::new(0),
|
||||||
|
posdao_transition,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn transact(&self, data: Bytes, nonce: U256) -> Result<(), EthcoreError> {
|
||||||
|
let client = self.client.read().as_ref().and_then(Weak::upgrade).ok_or("No client!")?;
|
||||||
|
let full_client = client.as_full_client().ok_or("No full client!")?;
|
||||||
|
|
||||||
|
let tx_request = TransactionRequest::call(self.contract_address, data).gas_price(U256::zero()).nonce(nonce);
|
||||||
|
match full_client.transact(tx_request) {
|
||||||
|
Ok(()) | Err(transaction::Error::AlreadyImported) => Ok(()),
|
||||||
|
Err(e) => Err(e)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Puts a malice report into the queue for later resending.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `addr` - The address of the misbehaving validator.
|
||||||
|
/// * `block` - The block number at which the misbehavior occurred.
|
||||||
|
/// * `data` - The call data for the `reportMalicious` contract call.
|
||||||
|
pub(crate) fn enqueue_report(&self, addr: Address, block: BlockNumber, data: Vec<u8>) {
|
||||||
|
// Skip the rest of the function unless there has been a transition to POSDAO AuRa.
|
||||||
|
if self.posdao_transition.map_or(true, |block_num| block < block_num) {
|
||||||
|
trace!(target: "engine", "Skipping queueing a malicious behavior report");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.report_queue.lock().push(addr, block, data)
|
||||||
|
}
|
||||||
|
|
||||||
/// Queries the state and gets the set of validators.
|
/// Queries the state and gets the set of validators.
|
||||||
fn get_list(&self, caller: &Call) -> Option<SimpleList> {
|
fn get_list(&self, caller: &Call) -> Option<SimpleList> {
|
||||||
let contract_address = self.contract_address;
|
let contract_address = self.contract_address;
|
||||||
@ -300,6 +343,85 @@ impl ValidatorSet for ValidatorSafeContract {
|
|||||||
.map(|out| (out, Vec::new()))) // generate no proofs in general
|
.map(|out| (out, Vec::new()))) // generate no proofs in general
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_engine_transactions(&self, _first: bool, header: &Header, caller: &mut SystemCall)
|
||||||
|
-> Result<Vec<(Address, Bytes)>, EthcoreError>
|
||||||
|
{
|
||||||
|
// Skip the rest of the function unless there has been a transition to POSDAO AuRa.
|
||||||
|
if self.posdao_transition.map_or(true, |block_num| header.number() < block_num) {
|
||||||
|
trace!(target: "engine", "Skipping a call to emitInitiateChange");
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
let mut transactions = Vec::new();
|
||||||
|
|
||||||
|
// Create the `InitiateChange` event if necessary.
|
||||||
|
let (data, decoder) = validator_set::functions::emit_initiate_change_callable::call();
|
||||||
|
let emit_initiate_change_callable = caller(self.contract_address, data)
|
||||||
|
.and_then(|x| decoder.decode(&x)
|
||||||
|
.map_err(|x| format!("chain spec bug: could not decode: {:?}", x)))
|
||||||
|
.map_err(EngineError::FailedSystemCall)?;
|
||||||
|
if !emit_initiate_change_callable {
|
||||||
|
trace!(target: "engine", "New block #{} issued ― no need to call emitInitiateChange()", header.number());
|
||||||
|
} else {
|
||||||
|
trace!(target: "engine", "New block issued #{} ― calling emitInitiateChange()", header.number());
|
||||||
|
let (data, _decoder) = validator_set::functions::emit_initiate_change::call();
|
||||||
|
transactions.push((self.contract_address, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = self.client.read().as_ref().and_then(Weak::upgrade).ok_or("No client!")?;
|
||||||
|
let client = client.as_full_client().ok_or("No full client!")?;
|
||||||
|
|
||||||
|
// Retry all pending reports.
|
||||||
|
let mut report_queue = self.report_queue.lock();
|
||||||
|
report_queue.filter(client, header.author(), self.contract_address);
|
||||||
|
for (_address, _block, data) in report_queue.iter().take(MAX_REPORTS_PER_BLOCK) {
|
||||||
|
transactions.push((self.contract_address, data.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(transactions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_close_block(&self, header: &Header, our_address: &Address) -> Result<(), EthcoreError> {
|
||||||
|
// Skip the rest of the function unless there has been a transition to POSDAO AuRa.
|
||||||
|
if self.posdao_transition.map_or(true, |block_num| header.number() < block_num) {
|
||||||
|
trace!(target: "engine", "Skipping resending of queued malicious behavior reports");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = self.client.read().as_ref().and_then(Weak::upgrade).ok_or("No client!")?;
|
||||||
|
let client = client.as_full_client().ok_or("No full client!")?;
|
||||||
|
|
||||||
|
let mut report_queue = self.report_queue.lock();
|
||||||
|
report_queue.filter(client, our_address, self.contract_address);
|
||||||
|
report_queue.truncate();
|
||||||
|
|
||||||
|
let mut resent_reports_in_block = self.resent_reports_in_block.lock();
|
||||||
|
|
||||||
|
// Skip at least one block after sending malicious reports last time.
|
||||||
|
if header.number() > *resent_reports_in_block + REPORTS_SKIP_BLOCKS {
|
||||||
|
*resent_reports_in_block = header.number();
|
||||||
|
let mut nonce = client.latest_nonce(our_address);
|
||||||
|
for (address, block, data) in report_queue.iter() {
|
||||||
|
debug!(target: "engine", "Retrying to report validator {} for misbehavior on block {} with nonce {}.",
|
||||||
|
address, block, nonce);
|
||||||
|
while match self.transact(data.clone(), nonce) {
|
||||||
|
Ok(()) => false,
|
||||||
|
Err(EthcoreError::Transaction(transaction::Error::Old)) => true,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(target: "engine", "Cannot report validator {} for misbehavior on block {}: {}",
|
||||||
|
address, block, err);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
warn!(target: "engine", "Nonce {} already used. Incrementing.", nonce);
|
||||||
|
nonce += U256::from(1);
|
||||||
|
}
|
||||||
|
nonce += U256::from(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn on_epoch_begin(&self, _first: bool, _header: &Header, caller: &mut SystemCall) -> Result<(), EthcoreError> {
|
fn on_epoch_begin(&self, _first: bool, _header: &Header, caller: &mut SystemCall) -> Result<(), EthcoreError> {
|
||||||
let data = validator_set::functions::finalize_change::encode_input();
|
let data = validator_set::functions::finalize_change::encode_input();
|
||||||
caller(self.contract_address, data)
|
caller(self.contract_address, data)
|
||||||
@ -450,6 +572,68 @@ impl ValidatorSet for ValidatorSafeContract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A queue containing pending reports of malicious validators.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct ReportQueue(VecDeque<(Address, BlockNumber, Vec<u8>)>);
|
||||||
|
|
||||||
|
impl ReportQueue {
|
||||||
|
/// Pushes a report to the end of the queue.
|
||||||
|
fn push(&mut self, addr: Address, block: BlockNumber, data: Vec<u8>) {
|
||||||
|
self.0.push_back((addr, block, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Filters reports of validators that have already been reported or are banned.
|
||||||
|
fn filter(
|
||||||
|
&mut self,
|
||||||
|
client: &dyn BlockChainClient,
|
||||||
|
our_address: &Address,
|
||||||
|
contract_address: Address,
|
||||||
|
) {
|
||||||
|
self.0.retain(|&(malicious_validator_address, block, ref _data)| {
|
||||||
|
trace!(
|
||||||
|
target: "engine",
|
||||||
|
"Checking if report of malicious validator {} at block {} should be removed from cache",
|
||||||
|
malicious_validator_address,
|
||||||
|
block
|
||||||
|
);
|
||||||
|
// Check if the validator should be reported.
|
||||||
|
let (data, decoder) = validator_set::functions::should_validator_report::call(
|
||||||
|
*our_address, malicious_validator_address, block
|
||||||
|
);
|
||||||
|
match client.call_contract(BlockId::Latest, contract_address, data)
|
||||||
|
.and_then(|result| decoder.decode(&result[..]).map_err(|e| e.to_string()))
|
||||||
|
{
|
||||||
|
Ok(false) => {
|
||||||
|
trace!(target: "engine", "Successfully removed report from report cache");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Ok(true) => true,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(target: "engine", "Failed to query report status {:?}, dropping pending report.", err);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all transactions in the queue.
|
||||||
|
fn iter(&self) -> impl Iterator<Item = &(Address, BlockNumber, Vec<u8>)> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes reports from the queue if it contains more than `MAX_QUEUED_REPORTS` entries.
|
||||||
|
fn truncate(&mut self) {
|
||||||
|
if self.0.len() > MAX_QUEUED_REPORTS {
|
||||||
|
warn!(
|
||||||
|
target: "engine",
|
||||||
|
"Removing {} reports from report cache, even though it has not been finalized",
|
||||||
|
self.0.len() - MAX_QUEUED_REPORTS
|
||||||
|
);
|
||||||
|
self.0.truncate(MAX_QUEUED_REPORTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -481,7 +665,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn fetches_validators() {
|
fn fetches_validators() {
|
||||||
let client = generate_dummy_client_with_spec(spec::new_validator_safe_contract);
|
let client = generate_dummy_client_with_spec(spec::new_validator_safe_contract);
|
||||||
let vc = Arc::new(ValidatorSafeContract::new("0000000000000000000000000000000000000005".parse::<Address>().unwrap()));
|
let addr: Address = "0000000000000000000000000000000000000005".parse().unwrap();
|
||||||
|
let vc = Arc::new(ValidatorSafeContract::new(addr, None));
|
||||||
vc.register_client(Arc::downgrade(&client) as _);
|
vc.register_client(Arc::downgrade(&client) as _);
|
||||||
let last_hash = client.best_block_header().hash();
|
let last_hash = client.best_block_header().hash();
|
||||||
assert!(vc.contains(&last_hash, &"7d577a597b2742b498cb5cf0c26cdcd726d39e6e".parse::<Address>().unwrap()));
|
assert!(vc.contains(&last_hash, &"7d577a597b2742b498cb5cf0c26cdcd726d39e6e".parse::<Address>().unwrap()));
|
||||||
|
@ -26,9 +26,10 @@ use common_types::{
|
|||||||
use ethereum_types::{H256, Address};
|
use ethereum_types::{H256, Address};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use machine::Machine;
|
use machine::Machine;
|
||||||
|
use parity_bytes::Bytes;
|
||||||
use parity_util_mem::MallocSizeOf;
|
use parity_util_mem::MallocSizeOf;
|
||||||
|
|
||||||
use super::ValidatorSet;
|
use super::{SystemCall, ValidatorSet};
|
||||||
|
|
||||||
/// Validator set containing a known set of addresses.
|
/// Validator set containing a known set of addresses.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Default, MallocSizeOf)]
|
#[derive(Clone, Debug, PartialEq, Eq, Default, MallocSizeOf)]
|
||||||
@ -71,6 +72,16 @@ impl ValidatorSet for SimpleList {
|
|||||||
Box::new(|_, _| Err("Simple list doesn't require calls.".into()))
|
Box::new(|_, _| Err("Simple list doesn't require calls.".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_engine_transactions(&self, _first: bool, _header: &Header, _call: &mut SystemCall)
|
||||||
|
-> Result<Vec<(Address, Bytes)>, EthcoreError>
|
||||||
|
{
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_close_block(&self, _header: &Header, _address: &Address) -> Result<(), EthcoreError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn is_epoch_end(&self, first: bool, _chain_head: &Header) -> Option<Vec<u8>> {
|
fn is_epoch_end(&self, first: bool, _chain_head: &Header) -> Option<Vec<u8>> {
|
||||||
match first {
|
match first {
|
||||||
true => Some(Vec::new()), // allow transition to fixed list, and instantly
|
true => Some(Vec::new()), // allow transition to fixed list, and instantly
|
||||||
|
@ -33,7 +33,7 @@ use ethereum_types::{H256, Address};
|
|||||||
use machine::Machine;
|
use machine::Machine;
|
||||||
use parity_bytes::Bytes;
|
use parity_bytes::Bytes;
|
||||||
|
|
||||||
use super::{ValidatorSet, SimpleList};
|
use super::{SystemCall, ValidatorSet, SimpleList};
|
||||||
|
|
||||||
/// Set used for testing with a single validator.
|
/// Set used for testing with a single validator.
|
||||||
#[derive(Clone, MallocSizeOf, Debug)]
|
#[derive(Clone, MallocSizeOf, Debug)]
|
||||||
@ -82,6 +82,16 @@ impl ValidatorSet for TestSet {
|
|||||||
Box::new(|_, _| Err("Test set doesn't require calls.".into()))
|
Box::new(|_, _| Err("Test set doesn't require calls.".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_engine_transactions(&self, _first: bool, _header: &Header, _call: &mut SystemCall)
|
||||||
|
-> Result<Vec<(Address, Bytes)>, EthcoreError>
|
||||||
|
{
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_close_block(&self, _header: &Header, _address: &Address) -> Result<(), EthcoreError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn is_epoch_end(&self, _first: bool, _chain_head: &Header) -> Option<Vec<u8>> { None }
|
fn is_epoch_end(&self, _first: bool, _chain_head: &Header) -> Option<Vec<u8>> { None }
|
||||||
|
|
||||||
fn signals_epoch_end(&self, _: bool, _: &Header, _: AuxiliaryData)
|
fn signals_epoch_end(&self, _: bool, _: &Header, _: AuxiliaryData)
|
||||||
|
File diff suppressed because one or more lines are too long
119
ethcore/res/validator_contract.sol
Normal file
119
ethcore/res/validator_contract.sol
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// Source for the test AuRa validator set contract. DO NOT USE IN PRODUCTION.
|
||||||
|
//
|
||||||
|
// Contains POSDAO features. The full POSDAO ValidatorSet contract production code is available at
|
||||||
|
// https://github.com/poanetwork/posdao-contracts/blob/master/contracts/ValidatorSetAuRa.sol
|
||||||
|
//
|
||||||
|
// The bytecode of this contract is included in `validator_contract.json` as the
|
||||||
|
// constructor of address `0x0000..0005`.
|
||||||
|
|
||||||
|
pragma solidity ^0.5.0;
|
||||||
|
|
||||||
|
contract TestValidatorSet {
|
||||||
|
|
||||||
|
address public disliked; // contains the address of validator reported by `reportBenign`
|
||||||
|
mapping(address => bool) public isValidatorBanned; // if the validator is banned by `reportMalicious`
|
||||||
|
|
||||||
|
// The initial set of validators
|
||||||
|
address[] public validators = [
|
||||||
|
0x7d577a597B2742b498Cb5Cf0C26cDCD726d39E6e,
|
||||||
|
0x82A978B3f5962A5b0957d9ee9eEf472EE55B42F1
|
||||||
|
];
|
||||||
|
|
||||||
|
// The mappings used by POSDAO features testing (see `reportMalicious` and `shouldValidatorReport` functions below)
|
||||||
|
mapping(address => mapping(uint256 => address[])) private _maliceReportedForBlock;
|
||||||
|
mapping(address => mapping(uint256 => mapping(address => bool))) private _maliceReportedForBlockMapped;
|
||||||
|
mapping(address => uint256) private _validatorIndex;
|
||||||
|
|
||||||
|
// The standard event to notify the engine about the validator set changing in the contract
|
||||||
|
event InitiateChange(bytes32 indexed parentHash, address[] newSet);
|
||||||
|
|
||||||
|
constructor() public {
|
||||||
|
// Initialize validator indices to be able to correctly remove
|
||||||
|
// a malicious validator from the validator set later
|
||||||
|
for (uint i = 0; i < validators.length; i++) {
|
||||||
|
_validatorIndex[validators[i]] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emits an `InitiateChange` event with the current (or new) validator set
|
||||||
|
function emitInitiateChange() public {
|
||||||
|
emit InitiateChange(blockhash(block.number - 1), validators);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applies a validator set change in production code. Does nothing in the test
|
||||||
|
function finalizeChange() pure public {}
|
||||||
|
|
||||||
|
// Benign validator behaviour report. Kept here for regression testing
|
||||||
|
function reportBenign(address _validator, uint256) public {
|
||||||
|
disliked = _validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes a malicious validator from the list
|
||||||
|
function reportMalicious(address _validator, uint256 _blockNum, bytes calldata) external {
|
||||||
|
address reportingValidator = msg.sender;
|
||||||
|
|
||||||
|
// Mark the `_validator` as reported by `reportingValidator` for the block `_blockNum`
|
||||||
|
_maliceReportedForBlock[_validator][_blockNum].push(reportingValidator);
|
||||||
|
_maliceReportedForBlockMapped[_validator][_blockNum][reportingValidator] = true;
|
||||||
|
isValidatorBanned[_validator] = true;
|
||||||
|
|
||||||
|
// If the passed validator is in the validator set
|
||||||
|
if (validators[_validatorIndex[_validator]] == _validator) {
|
||||||
|
// Remove the validator from the set
|
||||||
|
validators[_validatorIndex[_validator]] = validators[validators.length - 1];
|
||||||
|
delete _validatorIndex[_validator];
|
||||||
|
delete validators[validators.length - 1];
|
||||||
|
validators.length--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests validator set changing and emitting the `InitiateChange` event
|
||||||
|
function setValidators(address[] memory _validators) public {
|
||||||
|
validators = _validators;
|
||||||
|
emitInitiateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if `emitInitiateChange` can be called (used by POSDAO tests)
|
||||||
|
function emitInitiateChangeCallable() view public returns(bool) {
|
||||||
|
return block.number > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the current validator set
|
||||||
|
function getValidators() public view returns(address[] memory) {
|
||||||
|
return validators;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the list of all validators that reported the given validator
|
||||||
|
// as malicious for the given block. Used by POSDAO tests
|
||||||
|
function maliceReportedForBlock(address _validator, uint256 _blockNum) public view returns(address[] memory) {
|
||||||
|
return _maliceReportedForBlock[_validator][_blockNum];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a boolean flag indicating whether the specified validator
|
||||||
|
// should report about some validator's misbehaviour at the specified block.
|
||||||
|
// Used by POSDAO tests.
|
||||||
|
// `_reportingValidator` is the address of validator who reports.
|
||||||
|
// `_maliciousValidator` is the address of malicious validator.
|
||||||
|
// `_blockNumber` is the block number at which the malicious validator misbehaved.
|
||||||
|
function shouldValidatorReport(
|
||||||
|
address _reportingValidator,
|
||||||
|
address _maliciousValidator,
|
||||||
|
uint256 _blockNumber
|
||||||
|
) public view returns(bool) {
|
||||||
|
uint256 currentBlock = block.number;
|
||||||
|
if (_blockNumber > currentBlock) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (currentBlock > 100 && currentBlock - 100 > _blockNumber) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isValidatorBanned[_maliciousValidator]) {
|
||||||
|
// We shouldn't report the malicious validator
|
||||||
|
// as it has already been reported and banned
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Return `false` if already reported by the same `_reportingValidator` for the same `_blockNumber`
|
||||||
|
return !_maliceReportedForBlockMapped[_maliciousValidator][_blockNumber][_reportingValidator];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -100,6 +100,9 @@ pub struct AuthorityRoundParams {
|
|||||||
/// The addresses of contracts that determine the block gas limit starting from the block number
|
/// The addresses of contracts that determine the block gas limit starting from the block number
|
||||||
/// associated with each of those contracts.
|
/// associated with each of those contracts.
|
||||||
pub block_gas_limit_contract_transitions: Option<BTreeMap<Uint, Address>>,
|
pub block_gas_limit_contract_transitions: Option<BTreeMap<Uint, Address>>,
|
||||||
|
/// The block number at which the consensus engine switches from AuRa to AuRa with POSDAO
|
||||||
|
/// modifications.
|
||||||
|
pub posdao_transition: Option<Uint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authority engine deserialization.
|
/// Authority engine deserialization.
|
||||||
|
Loading…
Reference in New Issue
Block a user