light-fetch: Differentiate between out-of-gas/manual throw and use required gas from response on failure (#9824)

* fix start_gas, handle OOG exceptions & NotEnoughGas

* Change START_GAS: 50_000 -> 60_000
* When the `OutOfGas exception` is received then try to double the gas until it succeeds or block gas limit is reached
* When `NotEnoughBasGas error` is received then use the required gas provided in the response

* fix(light-fetch): ensure block_gas_limit is tried

Try the `block_gas_limit` before regard the execution as an error

* Update rpc/src/v1/helpers/light_fetch.rs

Co-Authored-By: niklasad1 <niklasadolfsson1@gmail.com>
This commit is contained in:
Niklas Adolfsson 2018-11-14 11:04:33 +01:00 committed by Andronik Ordian
parent a8617e2862
commit 23a29439c0

View File

@ -19,12 +19,12 @@
use std::cmp; use std::cmp;
use std::sync::Arc; use std::sync::Arc;
use light::on_demand::error::Error as OnDemandError;
use ethcore::basic_account::BasicAccount; use ethcore::basic_account::BasicAccount;
use ethcore::encoded; use ethcore::encoded;
use ethcore::filter::Filter as EthcoreFilter; use ethcore::filter::Filter as EthcoreFilter;
use ethcore::ids::BlockId; use ethcore::ids::BlockId;
use ethcore::receipt::Receipt; use ethcore::receipt::Receipt;
use ethcore::executed::ExecutionError;
use jsonrpc_core::{Result, Error}; use jsonrpc_core::{Result, Error};
use jsonrpc_core::futures::{future, Future}; use jsonrpc_core::futures::{future, Future};
@ -38,6 +38,7 @@ use light::on_demand::{
request, OnDemand, HeaderRef, Request as OnDemandRequest, request, OnDemand, HeaderRef, Request as OnDemandRequest,
Response as OnDemandResponse, ExecutionResult, Response as OnDemandResponse, ExecutionResult,
}; };
use light::on_demand::error::Error as OnDemandError;
use light::request::Field; use light::request::Field;
use sync::LightSync; use sync::LightSync;
@ -202,8 +203,8 @@ impl LightFetch {
/// Helper for getting proved execution. /// Helper for getting proved execution.
pub fn proved_read_only_execution(&self, req: CallRequest, num: Trailing<BlockNumber>) -> impl Future<Item = ExecutionResult, Error = Error> + Send { pub fn proved_read_only_execution(&self, req: CallRequest, num: Trailing<BlockNumber>) -> impl Future<Item = ExecutionResult, Error = Error> + Send {
const DEFAULT_GAS_PRICE: u64 = 21_000; const DEFAULT_GAS_PRICE: u64 = 21_000;
// starting gas when gas not provided. // (21000 G_transaction + 32000 G_create + some marginal to allow a few operations)
const START_GAS: u64 = 50_000; const START_GAS: u64 = 60_000;
let (sync, on_demand, client) = (self.sync.clone(), self.on_demand.clone(), self.client.clone()); let (sync, on_demand, client) = (self.sync.clone(), self.on_demand.clone(), self.client.clone());
let req: CallRequestHelper = req.into(); let req: CallRequestHelper = req.into();
@ -615,28 +616,41 @@ struct ExecuteParams {
sync: Arc<LightSync>, sync: Arc<LightSync>,
} }
// has a peer execute the transaction with given params. If `gas_known` is false, // Has a peer execute the transaction with given params. If `gas_known` is false, this will set the `gas value` to the
// this will double the gas on each `OutOfGas` error. // `required gas value` unless it exceeds the block gas limit
fn execute_read_only_tx(gas_known: bool, params: ExecuteParams) -> impl Future<Item = ExecutionResult, Error = Error> + Send { fn execute_read_only_tx(gas_known: bool, params: ExecuteParams) -> impl Future<Item = ExecutionResult, Error = Error> + Send {
if !gas_known { if !gas_known {
Box::new(future::loop_fn(params, |mut params| { Box::new(future::loop_fn(params, |mut params| {
execute_read_only_tx(true, params.clone()).and_then(move |res| { execute_read_only_tx(true, params.clone()).and_then(move |res| {
match res { match res {
Ok(executed) => { Ok(executed) => {
// TODO: how to distinguish between actual OOG and // `OutOfGas` exception, try double the gas
// exception? if let Some(vm::Error::OutOfGas) = executed.exception {
if executed.exception.is_some() { // block gas limit already tried, regard as an error and don't retry
let old_gas = params.tx.gas; if params.tx.gas >= params.hdr.gas_limit() {
params.tx.gas = params.tx.gas * 2u32; trace!(target: "light_fetch", "OutOutGas exception received, gas increase: failed");
if params.tx.gas > params.hdr.gas_limit() {
params.tx.gas = old_gas;
} else { } else {
params.tx.gas = cmp::min(params.tx.gas * 2_u32, params.hdr.gas_limit());
trace!(target: "light_fetch", "OutOutGas exception received, gas increased to {}",
params.tx.gas);
return Ok(future::Loop::Continue(params)) return Ok(future::Loop::Continue(params))
} }
} }
Ok(future::Loop::Break(Ok(executed))) Ok(future::Loop::Break(Ok(executed)))
} }
Err(ExecutionError::NotEnoughBaseGas { required, got }) => {
trace!(target: "light_fetch", "Not enough start gas provided required: {}, got: {}",
required, got);
if required <= params.hdr.gas_limit() {
params.tx.gas = required;
return Ok(future::Loop::Continue(params))
} else {
warn!(target: "light_fetch",
"Required gas is bigger than block header's gas dropping the request");
Ok(future::Loop::Break(Err(ExecutionError::NotEnoughBaseGas { required, got })))
}
}
// Non-recoverable execution error
failed => Ok(future::Loop::Break(failed)), failed => Ok(future::Loop::Break(failed)),
} }
}) })