Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ethcore/call-contract/src/call_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@

use bytes::Bytes;
use ethereum_types::Address;
use types::header::Header;
use types::ids::BlockId;

/// Provides `call_contract` method
pub trait CallContract {
/// Like `call`, but with various defaults. Designed to be used for calling contracts.
fn call_contract(&self, id: BlockId, address: Address, data: Bytes) -> Result<Bytes, String>;

/// Makes a constant call to a contract, at the beginning of the block corresponding to the given header, i.e. with
/// the EVM state after the block's parent, but with the new header's block number. Fails if the parent is not in
/// the database.
fn call_contract_before(&self, header: &Header, address: Address, data: Bytes) -> Result<Bytes, String>;
}

/// Provides information on a blockchain service and it's registry
Expand Down
6 changes: 5 additions & 1 deletion ethcore/private-tx/src/key_server_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ mod tests {

impl CallContract for DummyRegistryClient {
fn call_contract(&self, _id: BlockId, _address: Address, _data: Bytes) -> Result<Bytes, String> { Ok(vec![]) }

fn call_contract_before(&self, _header: &Header, _address: Address, _data: Bytes) -> Result<Bytes, String> {
Ok(vec![])
}
}

#[test]
Expand All @@ -170,4 +174,4 @@ mod tests {
keys_data.update_acl_contract();
assert_eq!(keys_data.keys_acl_contract.read().unwrap(), key.address());
}
}
}
16 changes: 16 additions & 0 deletions ethcore/res/contracts/block_gas_limit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
{
"constant": true,
"inputs": [],
"name": "blockGasLimit",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
43 changes: 43 additions & 0 deletions ethcore/src/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1456,6 +1456,49 @@ impl CallContract for Client {
.map_err(|e| format!("{:?}", e))
.map(|executed| executed.output)
}

fn call_contract_before(&self, header: &Header, address: Address, data: Bytes) -> Result<Bytes, String> {
let db = self.state_db.read().boxed_clone();

// early exit for pruned blocks
if db.is_pruned() && self.pruning_info().earliest_state > header.number() {
return Err(CallError::StatePruned.to_string());
}

let parent = self.chain.read().block_header_data(header.parent_hash()).ok_or_else(|| {
error!(target: "client", "Failed to call contract at {:?}'s child", header.parent_hash());
format!("Failed to call contract at {:?}'s child", header.parent_hash())
})?;
let root = parent.state_root();
let nonce = self.engine.account_start_nonce(header.number());
let mut state = State::from_existing(db, root, nonce, self.factories.clone())
.map_err(|_| CallError::StatePruned.to_string())?;

let from = Address::default();
let transaction = transaction::Transaction {
nonce: state.nonce(&from).unwrap_or_else(|_| self.engine.account_start_nonce(0)),
action: Action::Call(address),
gas: U256::from(50_000_000),
gas_price: U256::default(),
value: U256::default(),
data,
}.fake_sign(from);

let env_info = EnvInfo {
number: header.number(),
author: header.author().clone(),
timestamp: header.timestamp(),
difficulty: header.difficulty().clone(),
last_hashes: self.build_last_hashes(&parent.hash()),
gas_used: U256::default(),
gas_limit: U256::max_value(),
};
let machine = self.engine.machine();

Self::do_virtual_call(&machine, &env_info, &mut state, &transaction, Default::default())
.map_err(|e| format!("{:?}", e))
.map(|executed| executed.output)
}
}

impl ImportBlock for Client {
Expand Down
4 changes: 4 additions & 0 deletions ethcore/src/client/test_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,10 @@ impl BlockInfo for TestBlockChainClient {

impl CallContract for TestBlockChainClient {
fn call_contract(&self, _id: BlockId, _address: Address, _data: Bytes) -> Result<Bytes, String> { Ok(vec![]) }

fn call_contract_before(&self, _header: &Header, _address: Address, _data: Bytes) -> Result<Bytes, String> {
Ok(vec![])
}
}

impl TransactionInfo for TestBlockChainClient {
Expand Down
43 changes: 42 additions & 1 deletion ethcore/src/engines/authority_round/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ use engines::{Engine, Seal, SealingState, EngineError, ConstructedVerifier};
use engines::block_reward;
use engines::block_reward::{BlockRewardContract, RewardKind};
use error::{Error, ErrorKind, BlockError};
use ethjson::{spec::StepDuration};
use ethabi::FunctionOutputDecoder;
use ethjson::{self, spec::StepDuration};
use machine::{AuxiliaryData, Call, EthereumMachine};
use hash::keccak;
use super::signer::EngineSigner;
Expand All @@ -53,6 +54,8 @@ use unexpected::{Mismatch, OutOfBounds};
#[cfg(not(time_checked_add))]
use time_utils::CheckedSystemTime;

use_contract!(block_gas_limit, "res/contracts/block_gas_limit.json");

mod finality;
mod randomness;
pub(crate) mod util;
Expand Down Expand Up @@ -1107,6 +1110,14 @@ impl Engine<EthereumMachine> for AuthorityRound {

let score = calculate_score(parent_step, current_step, current_empty_steps_len);
header.set_difficulty(score);
if let Some(gas_limit) = self.gas_limit_override(header) {
trace!(target: "engine", "Setting gas limit to {} for block {}.", gas_limit, header.number());
let parent_gas_limit = *parent.gas_limit();
header.set_gas_limit(gas_limit);
if parent_gas_limit != gas_limit {
info!(target: "engine", "Block gas limit was changed from {} to {}.", parent_gas_limit, gas_limit);
}
}
}

fn sealing_state(&self) -> SealingState {
Expand Down Expand Up @@ -1783,6 +1794,36 @@ impl Engine<EthereumMachine> for AuthorityRound {

finalized.into_iter().map(AncestryAction::MarkFinalized).collect()
}

fn gas_limit_override(&self, header: &Header) -> Option<U256> {
let (_, &address) = self.machine.params().block_gas_limit_contract.range(..=header.number()).last()?;

let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) {
Some(client) => client,
None => {
debug!(target: "engine", "Unable to prepare block: missing client ref.");
return None;
}
};
let full_client = match client.as_full_client() {
Some(full_client) => full_client,
None => {
debug!(target: "engine", "Failed to upgrade to BlockchainClient.");
return None;
}
};

let (data, decoder) = block_gas_limit::functions::block_gas_limit::call();
let value = full_client.call_contract_before(header, address, data).map_err(|err| {
error!(target: "engine", "Failed to call blockGasLimit. Not changing the block gas limit. {:?}", err);
Comment thread
afck marked this conversation as resolved.
}).ok()?;
if value.is_empty() {
debug!(target: "engine", "blockGasLimit returned nothing. Not changing the block gas limit.");
None
} else {
decoder.decode(&value).ok()
}
}
}

#[cfg(test)]
Expand Down
6 changes: 6 additions & 0 deletions ethcore/src/engines/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,12 @@ pub trait Engine<M: Machine>: Sync + Send {

/// Check whether the given new block is the best block, after finalization check.
fn fork_choice(&self, new: &M::ExtendedHeader, best: &M::ExtendedHeader) -> ForkChoice;

/// Overrides the block gas limit. Whenever this returns `Some` for a header, the next block's gas limit must be
/// exactly that value.
fn gas_limit_override(&self, _header: &Header) -> Option<U256> {
None
}
}

/// Check whether a given block is the best block based on the default total difficulty rule.
Expand Down
5 changes: 5 additions & 0 deletions ethcore/src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ impl EthereumMachine {
pub fn ethash_extensions(&self) -> Option<&EthashExtensions> {
self.ethash_extensions.as_ref()
}

/// Get a reference to the transaction filter, if present.
pub fn tx_filter(&self) -> Option<&Arc<TransactionFilter>> {
self.tx_filter.as_ref()
}
}

impl EthereumMachine {
Expand Down
3 changes: 3 additions & 0 deletions ethcore/src/spec/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ pub struct CommonParams {
pub subprotocol_name: String,
/// Minimum gas limit.
pub min_gas_limit: U256,
/// The address of a contract that determines the block gas limit.
pub block_gas_limit_contract: BTreeMap<BlockNumber, Address>,
/// Fork block to check.
pub fork_block: Option<(BlockNumber, H256)>,
/// EIP150 transition block number.
Expand Down Expand Up @@ -243,6 +245,7 @@ impl From<ethjson::spec::Params> for CommonParams {
},
subprotocol_name: p.subprotocol_name.unwrap_or_else(|| "eth".to_owned()),
min_gas_limit: p.min_gas_limit.into(),
block_gas_limit_contract: p.block_gas_limit_contract.map(|bglc| bglc.into()).unwrap_or_default(),
fork_block: if let (Some(n), Some(h)) = (p.fork_block, p.fork_hash) {
Some((n.into(), h.into()))
} else {
Expand Down
5 changes: 5 additions & 0 deletions ethcore/src/tx_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ impl TransactionFilter {
)
}

/// Get a reference to the contract address.
pub fn contract_address(&self) -> &Address {
&self.contract_address
}

/// Check if transaction is allowed at given block.
pub fn transaction_allowed<C: BlockInfo + CallContract>(&self, parent_hash: &H256, block_number: BlockNumber, transaction: &SignedTransaction, client: &C) -> bool {
if block_number < self.transition_block { return true; }
Expand Down
39 changes: 24 additions & 15 deletions ethcore/src/verification/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,21 @@ pub fn verify_header_params(header: &Header, engine: &EthEngine, is_full: bool,
if header.gas_used() > header.gas_limit() {
return Err(From::from(BlockError::TooMuchGasUsed(OutOfBounds { max: Some(*header.gas_limit()), min: None, found: *header.gas_used() })));
}
let min_gas_limit = engine.params().min_gas_limit;
if header.gas_limit() < &min_gas_limit {
return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas_limit), max: None, found: *header.gas_limit() })));
}
if let Some(limit) = engine.maximum_gas_limit() {
if header.gas_limit() > &limit {
return Err(From::from(::error::BlockError::InvalidGasLimit(OutOfBounds { min: None, max: Some(limit), found: *header.gas_limit() })));
if let Some(gas_limit) = engine.gas_limit_override(header) {
if *header.gas_limit() != gas_limit {
return Err(From::from(BlockError::InvalidGasLimit(
OutOfBounds { min: Some(gas_limit), max: Some(gas_limit), found: *header.gas_limit() }
)));
}
} else {
let min_gas_limit = engine.params().min_gas_limit;
if header.gas_limit() < &min_gas_limit {
return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas_limit), max: None, found: *header.gas_limit() })));
}
if let Some(limit) = engine.maximum_gas_limit() {
if header.gas_limit() > &limit {
return Err(From::from(::error::BlockError::InvalidGasLimit(OutOfBounds { min: None, max: Some(limit), found: *header.gas_limit() })));
}
}
}
let maximum_extra_data_size = engine.maximum_extra_data_size();
Expand Down Expand Up @@ -325,13 +333,11 @@ pub fn verify_header_params(header: &Header, engine: &EthEngine, is_full: bool,
Ok(())
}

/// Check header parameters agains parent header.
/// Check header parameters against parent header.
fn verify_parent(header: &Header, parent: &Header, engine: &EthEngine) -> Result<(), Error> {
assert!(header.parent_hash().is_zero() || &parent.hash() == header.parent_hash(),
"Parent hash should already have been verified; qed");

let gas_limit_divisor = engine.params().gas_limit_bound_divisor;

if !engine.is_timestamp_valid(header.timestamp(), parent.timestamp()) {
let now = SystemTime::now();
let min = now.checked_add(Duration::from_secs(parent.timestamp().saturating_add(1)))
Expand All @@ -348,11 +354,14 @@ fn verify_parent(header: &Header, parent: &Header, engine: &EthEngine) -> Result
return Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }).into());
}

let parent_gas_limit = *parent.gas_limit();
let min_gas = parent_gas_limit - parent_gas_limit / gas_limit_divisor;
let max_gas = parent_gas_limit + parent_gas_limit / gas_limit_divisor;
if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas {
return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: *header.gas_limit() })));
if engine.gas_limit_override(header).is_none() {
let gas_limit_divisor = engine.params().gas_limit_bound_divisor;
let parent_gas_limit = *parent.gas_limit();
let min_gas = parent_gas_limit - parent_gas_limit / gas_limit_divisor;
let max_gas = parent_gas_limit + parent_gas_limit / gas_limit_divisor;
if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas {
return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: *header.gas_limit() })));
}
}

Ok(())
Expand Down
39 changes: 36 additions & 3 deletions json/src/spec/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

//! Spec params deserialization.

use std::collections::BTreeMap;
use std::iter;
use ethereum_types::H160;
use uint::{self, Uint};
use hash::{H256, Address};
use bytes::Bytes;
Expand All @@ -31,6 +34,8 @@ pub struct Params {
pub maximum_extra_data_size: Uint,
/// Minimum gas limit.
pub min_gas_limit: Uint,
/// The address of a contract that determines the block gas limit.
pub block_gas_limit_contract: Option<BlockGasLimitContract>,

/// Network id.
#[serde(rename = "networkID")]
Expand Down Expand Up @@ -126,12 +131,36 @@ pub struct Params {
pub kip6_transition: Option<Uint>,
}

/// A contract that determines block gas limits can be configured either as a single address, or as a map, assigning
/// to each starting block number the contract address that should be used from that block on.
#[derive(Debug, PartialEq, Deserialize)]
#[serde(deny_unknown_fields, untagged)]
pub enum BlockGasLimitContract {
/// A single address for a block gas limit contract.
Single(Address),
/// A map from block numbers to the contract addresses that should be used for the gas limit after that block.
Transitions(BTreeMap<Uint, Address>),
}

impl From<BlockGasLimitContract> for BTreeMap<u64, H160> {
fn from(contract: BlockGasLimitContract) -> Self {
match contract {
BlockGasLimitContract::Single(Address(addr)) => iter::once((0, addr)).collect(),
BlockGasLimitContract::Transitions(transitions) => {
transitions.into_iter().map(|(Uint(block), Address(addr))| (block.into(), addr)).collect()
}
}
}
}

#[cfg(test)]
mod tests {
use std::str::FromStr;
use serde_json;
use uint::Uint;
use ethereum_types::U256;
use spec::params::Params;
use ethereum_types::{U256, H160};
use hash::Address;
use spec::params::{BlockGasLimitContract, Params};

#[test]
fn params_deserialization() {
Expand All @@ -144,7 +173,11 @@ mod tests {
"accountStartNonce": "0x01",
"gasLimitBoundDivisor": "0x20",
"maxCodeSize": "0x1000",
"wasmActivationTransition": "0x1010"
"wasmActivationTransition": "0x1010",
"blockGasLimitContract": {
"10": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"20": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
}
}"#;

let deserialized: Params = serde_json::from_str(s).unwrap();
Expand Down