Allow to distinguish out of gas from other traps (#4883)

* contracts: Allow to distinguish out of gas from other traps

When a contract encounters a runtime error a wasm trap is
triggered and the execution is halted. Currently, no matter
what was the cause for the trap it is always reported as:
DispatchError::Other("contract trapped during execution").

However, the trap that is triggered if a contract exhausts
its gas budget is particulary interesting. Therefore we add
a seperate error message for this cause:
DispatchError::Other("ran out of gas during contract execution").

A test is added hat executes a contract that never terminates.
Therefore it always exhausts is gas budget.

* fixup! contracts: Allow to distinguish out of gas from other traps

Remove overlong lines.

* fixup! contracts: Allow to distinguish out of gas from other traps

Rename Contract to Contracts
This commit is contained in:
Alexander Theißen
2020-02-14 11:46:45 +01:00
committed by GitHub
parent 5b7512e2e4
commit b999911bcf
3 changed files with 113 additions and 12 deletions
+67 -11
View File
@@ -39,6 +39,8 @@ const TRAP_RETURN_CODE: u32 = 0x0100;
enum SpecialTrap {
/// Signals that trap was generated in response to call `ext_return` host function.
Return(Vec<u8>),
/// Signals that trap was generated because the contract exhausted its gas limit.
OutOfGas,
}
/// Can only be used for one call.
@@ -74,9 +76,21 @@ pub(crate) fn to_execution_result<E: Ext>(
runtime: Runtime<E>,
sandbox_result: Result<sp_sandbox::ReturnValue, sp_sandbox::Error>,
) -> ExecResult {
// Special case. The trap was the result of the execution `return` host function.
if let Some(SpecialTrap::Return(data)) = runtime.special_trap {
return Ok(ExecReturnValue { status: STATUS_SUCCESS, data });
match runtime.special_trap {
// The trap was the result of the execution `return` host function.
Some(SpecialTrap::Return(data)) => {
return Ok(ExecReturnValue {
status: STATUS_SUCCESS,
data,
})
}
Some(SpecialTrap::OutOfGas) => {
return Err(ExecError {
reason: "ran out of gas during contract execution".into(),
buffer: runtime.scratch_buf,
})
}
_ => (),
}
// Check the exact type of the error.
@@ -179,11 +193,15 @@ impl<T: Trait> Token<T> for RuntimeToken {
fn charge_gas<T: Trait, Tok: Token<T>>(
gas_meter: &mut GasMeter<T>,
metadata: &Tok::Metadata,
special_trap: &mut Option<SpecialTrap>,
token: Tok,
) -> Result<(), sp_sandbox::HostError> {
match gas_meter.charge(metadata, token) {
GasMeterResult::Proceed => Ok(()),
GasMeterResult::OutOfGas => Err(sp_sandbox::HostError),
GasMeterResult::OutOfGas => {
*special_trap = Some(SpecialTrap::OutOfGas);
Err(sp_sandbox::HostError)
},
}
}
@@ -200,7 +218,12 @@ fn read_sandbox_memory<E: Ext>(
ptr: u32,
len: u32,
) -> Result<Vec<u8>, sp_sandbox::HostError> {
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(len))?;
charge_gas(
ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::ReadMemory(len),
)?;
let mut buf = vec![0u8; len as usize];
ctx.memory.get(ptr, buf.as_mut_slice()).map_err(|_| sp_sandbox::HostError)?;
@@ -220,7 +243,12 @@ fn read_sandbox_memory_into_scratch<E: Ext>(
ptr: u32,
len: u32,
) -> Result<(), sp_sandbox::HostError> {
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(len))?;
charge_gas(
ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::ReadMemory(len),
)?;
ctx.scratch_buf.resize(len as usize, 0);
ctx.memory.get(ptr, ctx.scratch_buf.as_mut_slice()).map_err(|_| sp_sandbox::HostError)?;
@@ -240,7 +268,12 @@ fn read_sandbox_memory_into_buf<E: Ext>(
ptr: u32,
buf: &mut [u8],
) -> Result<(), sp_sandbox::HostError> {
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(buf.len() as u32))?;
charge_gas(
ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::ReadMemory(buf.len() as u32),
)?;
ctx.memory.get(ptr, buf).map_err(Into::into)
}
@@ -273,12 +306,18 @@ fn read_sandbox_memory_as<E: Ext, D: Decode>(
/// - designated area is not within the bounds of the sandbox memory.
fn write_sandbox_memory<T: Trait>(
schedule: &Schedule,
special_trap: &mut Option<SpecialTrap>,
gas_meter: &mut GasMeter<T>,
memory: &sp_sandbox::Memory,
ptr: u32,
buf: &[u8],
) -> Result<(), sp_sandbox::HostError> {
charge_gas(gas_meter, schedule, RuntimeToken::WriteMemory(buf.len() as u32))?;
charge_gas(
gas_meter,
schedule,
special_trap,
RuntimeToken::WriteMemory(buf.len() as u32),
)?;
memory.set(ptr, buf)?;
@@ -300,7 +339,12 @@ define_env!(Env, <E: Ext>,
//
// - amount: How much gas is used.
gas(ctx, amount: u32) => {
charge_gas(&mut ctx.gas_meter, ctx.schedule, RuntimeToken::Explicit(amount))?;
charge_gas(
&mut ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::Explicit(amount)
)?;
Ok(())
},
@@ -520,7 +564,12 @@ define_env!(Env, <E: Ext>,
//
// This is the only way to return a data buffer to the caller.
ext_return(ctx, data_ptr: u32, data_len: u32) => {
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReturnData(data_len))?;
charge_gas(
ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::ReturnData(data_len)
)?;
read_sandbox_memory_into_scratch(ctx, data_ptr, data_len)?;
let output_buf = mem::replace(&mut ctx.scratch_buf, Vec::new());
@@ -652,7 +701,12 @@ define_env!(Env, <E: Ext>,
let balance_fee = <<E as Ext>::T as Trait>::ComputeDispatchFee::compute_dispatch_fee(&call);
approx_gas_for_balance(ctx.gas_meter.gas_price(), balance_fee)
};
charge_gas(&mut ctx.gas_meter, ctx.schedule, RuntimeToken::ComputedDispatchFee(fee))?;
charge_gas(
&mut ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::ComputedDispatchFee(fee)
)?;
ctx.ext.note_dispatch_call(call);
@@ -756,6 +810,7 @@ define_env!(Env, <E: Ext>,
// Finally, perform the write.
write_sandbox_memory(
ctx.schedule,
&mut ctx.special_trap,
ctx.gas_meter,
&ctx.memory,
dest_ptr,
@@ -803,6 +858,7 @@ define_env!(Env, <E: Ext>,
charge_gas(
ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::DepositEvent(topics.len() as u32, data_len)
)?;
ctx.ext.deposit_event(topics, event_data);