Skip to content

Commit

Permalink
Merge pull request #140 from poanetwork/afck-gas-limit
Browse files Browse the repository at this point in the history
Set gas limit according to TxPermission.blockGasLimit.
  • Loading branch information
varasev authored Jul 15, 2019
2 parents 1c62c33 + 1207d1e commit 31e72e2
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 20 deletions.
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);
}).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

0 comments on commit 31e72e2

Please sign in to comment.