Merge pull request #6967 from paritytech/wasm-elog

Events in WASM runtime
This commit is contained in:
Alexey 2017-11-10 18:03:32 +03:00 committed by GitHub
commit df49b4b065
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 2 deletions

@ -1 +1 @@
Subproject commit c8129ce2f36c26ed634eda786960978a28e28d0e
Subproject commit 94b7877b5826a53627b8732ea0feb45869dd04ab

View File

@ -147,6 +147,11 @@ pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[
&[I32],
None,
),
Static(
"_elog",
&[I32; 4],
None,
),
// TODO: Get rid of it also somehow?
Static(

View File

@ -55,6 +55,8 @@ pub enum UserTrap {
Unknown,
/// Passed string had invalid utf-8 encoding
BadUtf8,
/// Log event error
Log,
/// Other error in native code
Other,
/// Panic with message
@ -75,6 +77,7 @@ impl ::std::fmt::Display for UserTrap {
UserTrap::AllocationFailed => write!(f, "Memory allocation failed (OOM)"),
UserTrap::BadUtf8 => write!(f, "String encoding is bad utf-8 sequence"),
UserTrap::GasLimit => write!(f, "Invocation resulted in gas limit violated"),
UserTrap::Log => write!(f, "Error occured while logging an event"),
UserTrap::Other => write!(f, "Other unspecified error"),
UserTrap::Panic(ref msg) => write!(f, "Panic: {}", msg),
}
@ -232,6 +235,21 @@ impl<'a, 'b> Runtime<'a, 'b> {
}
}
pub fn overflow_charge<F>(&mut self, f: F) -> Result<(), InterpreterError>
where F: FnOnce(&vm::Schedule) -> Option<u64>
{
let amount = match f(self.ext.schedule()) {
Some(amount) => amount,
None => { return Err(UserTrap::GasLimit.into()); }
};
if !self.charge_gas(amount as u64) {
Err(UserTrap::GasLimit.into())
} else {
Ok(())
}
}
/// Invoke create in the state runtime
pub fn create(&mut self, context: InterpreterCallerContext)
-> Result<Option<interpreter::RuntimeValue>, InterpreterError>
@ -749,6 +767,44 @@ impl<'a, 'b> Runtime<'a, 'b> {
pub fn ext(&mut self) -> &mut vm::Ext {
self.ext
}
pub fn log(&mut self, context: InterpreterCallerContext)
-> Result<Option<interpreter::RuntimeValue>, InterpreterError>
{
// signature is:
// pub fn elog(topic_ptr: *const u8, topic_count: u32, data_ptr: *const u8, data_len: u32);
let data_len = context.value_stack.pop_as::<i32>()? as u32;
let data_ptr = context.value_stack.pop_as::<i32>()? as u32;
let topic_count = context.value_stack.pop_as::<i32>()? as u32;
let topic_ptr = context.value_stack.pop_as::<i32>()? as u32;
if topic_count > 4 {
return Err(UserTrap::Log.into());
}
self.overflow_charge(|schedule|
{
let topics_gas = schedule.log_gas as u64 + schedule.log_topic_gas as u64 * topic_count as u64;
(schedule.log_data_gas as u64)
.checked_mul(schedule.log_data_gas as u64)
.and_then(|data_gas| data_gas.checked_add(topics_gas))
}
)?;
let mut topics: Vec<H256> = Vec::with_capacity(topic_count as usize);
topics.resize(topic_count as usize, H256::zero());
for i in 0..topic_count {
let offset = i.checked_mul(32).ok_or(UserTrap::MemoryAccessViolation)?
.checked_add(topic_ptr).ok_or(UserTrap::MemoryAccessViolation)?;
*topics.get_mut(i as usize)
.expect("topics is resized to `topic_count`, i is in 0..topic count iterator, get_mut uses i as an indexer, get_mut cannot fail; qed")
= H256::from(&self.memory.get(offset, 32)?[..]);
}
self.ext.log(topics, &self.memory.get(data_ptr, data_len as usize)?).map_err(|_| UserTrap::Log)?;
Ok(None)
}
}
impl<'a, 'b> interpreter::UserFunctionExecutor<UserTrap> for Runtime<'a, 'b> {
@ -833,6 +889,9 @@ impl<'a, 'b> interpreter::UserFunctionExecutor<UserTrap> for Runtime<'a, 'b> {
"_value" => {
self.value(context)
},
"_elog" => {
self.log(context)
},
_ => {
trace!(target: "wasm", "Trapped due to unhandled function: '{}'", name);
Ok(self.unknown_trap(context)?)

View File

@ -680,7 +680,6 @@ fn externs() {
#[test]
fn embedded_keccak() {
::ethcore_logger::init_log();
let mut code = load_sample!("keccak.wasm");
code.extend_from_slice(b"something");
@ -703,4 +702,39 @@ fn embedded_keccak() {
assert_eq!(H256::from_slice(&result), H256::from("68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87"));
assert_eq!(gas_left, U256::from(80_452));
}
/// This test checks the correctness of log extern
/// Target test puts one event with two topic [keccak(input), reverse(keccak(input))]
/// and reversed input as a data
#[test]
fn events() {
::ethcore_logger::init_log();
let code = load_sample!("events.wasm");
let mut params = ActionParams::default();
params.gas = U256::from(100_000);
params.code = Some(Arc::new(code));
params.data = Some(b"something".to_vec());
let mut ext = FakeExt::new();
let (gas_left, result) = {
let mut interpreter = wasm_interpreter();
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
match result {
GasLeft::Known(_) => { panic!("events should return payload"); },
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
}
};
assert_eq!(ext.logs.len(), 1);
let log_entry = &ext.logs[0];
assert_eq!(log_entry.topics.len(), 2);
assert_eq!(&log_entry.topics[0], &H256::from("68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87"));
assert_eq!(&log_entry.topics[1], &H256::from("871d5ea37430753faab7dff7a7187783517d83bd822c02e28a164c887e1d3768"));
assert_eq!(&log_entry.data, b"gnihtemos");
assert_eq!(&result, b"gnihtemos");
assert_eq!(gas_left, U256::from(78039));
}