-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EVM runtime: implement LOG{0..4} opcodes by emitting actor events (FIP-0049) #839
Changes from 12 commits
9d74736
abfa385
315eab9
f47cc3b
c66827c
454b133
881399c
0f0a1ef
982dd43
d3ee933
11bfaa7
f6a0fbe
49cd2c7
ff83bba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -390,15 +390,19 @@ where | |
let res = self.rt.send(to, method, params.clone(), value.clone()); | ||
|
||
let rec = match res { | ||
Ok(bytes) => { | ||
Receipt { exit_code: ExitCode::OK, return_data: bytes, gas_used: fake_gas_used } | ||
} | ||
Ok(bytes) => Receipt { | ||
exit_code: ExitCode::OK, | ||
return_data: bytes, | ||
gas_used: fake_gas_used, | ||
events_root: None, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ruuuuuuust, thats when you extra hate this lang. |
||
}, | ||
Err(ae) => { | ||
info!("datacap messenger failed: {}", ae.msg()); | ||
Receipt { | ||
exit_code: ae.exit_code(), | ||
return_data: RawBytes::default(), | ||
gas_used: fake_gas_used, | ||
events_root: None, | ||
} | ||
} | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,57 @@ | ||
use crate::interpreter::instructions::memory::get_memory_region; | ||
use fvm_ipld_encoding::{to_vec, RawBytes}; | ||
use fvm_shared::event::{Entry, Flags}; | ||
use { | ||
crate::interpreter::{ExecutionState, StatusCode, System}, | ||
fil_actors_runtime::runtime::Runtime, | ||
}; | ||
|
||
#[cfg(debug_assertions)] | ||
pub fn log( | ||
_state: &mut ExecutionState, | ||
_system: &System<impl Runtime>, | ||
_num_topics: usize, | ||
) -> Result<(), StatusCode> { | ||
todo!("unimplemented"); | ||
} | ||
/// The event key for the Ethereum log data. | ||
const EVENT_DATA_KEY: &str = "data"; | ||
|
||
/// The event keys for the Ethereum log topics. | ||
const EVENT_TOPIC_KEYS: &[&str] = &["topic1", "topic2", "topic3", "topic4"]; | ||
|
||
#[cfg(not(debug_assertions))] | ||
#[inline] | ||
pub fn log( | ||
state: &mut ExecutionState, | ||
_system: &System<impl Runtime>, | ||
system: &System<impl Runtime>, | ||
num_topics: usize, | ||
) -> Result<(), StatusCode> { | ||
// TODO: Right now, we just drop everything. But we implement this in production anyways so | ||
// things work. | ||
for _ in 0..num_topics { | ||
state.stack.pop(); | ||
// Handle the data. | ||
// Passing in a zero-sized memory region omits the data key entirely. | ||
// LOG0 + a zero-sized memory region emits an event with no entries whatsoever. In this case, | ||
// the FVM will record a hollow event carrying only the emitter actor ID. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this useful? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ethereum can record events with no topics nor data; the only useful thing is the emitter address in that case. Not sure how much it gets used in practice, but it is possible with that runtime. So not doing it here would break compatibility. |
||
let mem_index = state.stack.pop(); | ||
let size = state.stack.pop(); | ||
let region = get_memory_region(&mut state.memory, mem_index, size) | ||
.map_err(|_| StatusCode::InvalidMemoryAccess)?; | ||
|
||
// Extract the topics. Prefer to allocate an extra item than to incur in the cost of a | ||
// decision based on the size of the data. | ||
let mut entries: Vec<Entry> = Vec::with_capacity(num_topics + 1); | ||
for key in EVENT_TOPIC_KEYS.iter().take(num_topics) { | ||
let topic = state.stack.pop(); | ||
let entry = Entry { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might want to consider using |
||
flags: Flags::FLAG_INDEXED_VALUE, | ||
key: (*key).to_owned(), | ||
value: to_vec(&topic)?.into(), // U256 serializes as a byte string. | ||
}; | ||
entries.push(entry); | ||
} | ||
|
||
// Skip adding the data if it's zero-sized. | ||
if let Some(r) = region { | ||
let data = state.memory[r.offset..r.offset + r.size.get()].to_vec(); | ||
let entry = Entry { | ||
flags: Flags::FLAG_INDEXED_VALUE, | ||
key: EVENT_DATA_KEY.to_owned(), | ||
value: to_vec(&RawBytes::new(data))?.into(), | ||
raulk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
entries.push(entry); | ||
} | ||
|
||
system.rt.emit_event(&entries.into())?; | ||
|
||
Ok(()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
mod asm; | ||
|
||
use fvm_ipld_encoding::{to_vec, RawBytes}; | ||
use fvm_shared::event::{ActorEvent, Entry, Flags}; | ||
|
||
mod util; | ||
|
||
#[allow(dead_code)] | ||
pub fn events_contract() -> Vec<u8> { | ||
let init = r#" | ||
"#; | ||
let body = r#" | ||
# method dispatch: | ||
# - 0x00000000 -> log_zero_data | ||
# - 0x00000001 -> log_zero_nodata | ||
# - 0x00000002 -> log_four_data | ||
|
||
%dispatch_begin() | ||
%dispatch(0x00, log_zero_data) | ||
%dispatch(0x01, log_zero_nodata) | ||
%dispatch(0x02, log_four_data) | ||
%dispatch_end() | ||
|
||
#### log a zero topic event with data | ||
log_zero_data: | ||
jumpdest | ||
push8 0x1122334455667788 | ||
push1 0x00 | ||
mstore | ||
push1 0x08 | ||
push1 0x18 ## index 24 into memory as mstore writes a full word | ||
log0 | ||
push1 0x00 | ||
push1 0x00 | ||
return | ||
|
||
#### log a zero topic event with no data | ||
log_zero_nodata: | ||
jumpdest | ||
push1 0x00 | ||
push1 0x00 | ||
log0 | ||
push1 0x00 | ||
push1 0x00 | ||
return | ||
|
||
#### log a four topic event with data | ||
log_four_data: | ||
jumpdest | ||
push8 0x1122334455667788 | ||
push1 0x00 | ||
mstore | ||
push4 0x4444 | ||
push3 0x3333 | ||
push2 0x2222 | ||
push2 0x1111 | ||
push1 0x08 | ||
push1 0x18 ## index 24 into memory as mstore writes a full word | ||
log4 | ||
push1 0x00 | ||
push1 0x00 | ||
return | ||
|
||
"#; | ||
|
||
asm::new_contract("events", init, body).unwrap() | ||
} | ||
|
||
#[test] | ||
fn test_events() { | ||
let contract = events_contract(); | ||
|
||
let mut rt = util::construct_and_verify(contract); | ||
|
||
// log zero with data | ||
let mut contract_params = vec![0u8; 32]; | ||
rt.expect_emitted_event(ActorEvent { | ||
entries: vec![Entry { | ||
flags: Flags::FLAG_INDEXED_VALUE, | ||
key: "data".to_string(), | ||
value: to_vec(&RawBytes::from( | ||
[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88].to_vec(), | ||
)) | ||
.unwrap() | ||
.into(), | ||
}], | ||
}); | ||
util::invoke_contract(&mut rt, &contract_params); | ||
|
||
// log zero without data | ||
contract_params[3] = 0x01; | ||
rt.expect_emitted_event(ActorEvent { entries: vec![] }); | ||
util::invoke_contract(&mut rt, &contract_params); | ||
|
||
// log four with data | ||
contract_params[3] = 0x02; | ||
rt.expect_emitted_event(ActorEvent { | ||
entries: vec![ | ||
Entry { | ||
flags: Flags::FLAG_INDEXED_VALUE, | ||
key: "topic1".to_string(), | ||
value: to_vec(&RawBytes::from([0x11, 0x11].to_vec())).unwrap().into(), | ||
}, | ||
Entry { | ||
flags: Flags::FLAG_INDEXED_VALUE, | ||
key: "topic2".to_string(), | ||
value: to_vec(&RawBytes::from([0x22, 0x22].to_vec())).unwrap().into(), | ||
}, | ||
Entry { | ||
flags: Flags::FLAG_INDEXED_VALUE, | ||
key: "topic3".to_string(), | ||
value: to_vec(&RawBytes::from([0x33, 0x33].to_vec())).unwrap().into(), | ||
}, | ||
Entry { | ||
flags: Flags::FLAG_INDEXED_VALUE, | ||
key: "topic4".to_string(), | ||
value: to_vec(&RawBytes::from([0x44, 0x44].to_vec())).unwrap().into(), | ||
}, | ||
Entry { | ||
flags: Flags::FLAG_INDEXED_VALUE, | ||
key: "data".to_string(), | ||
value: to_vec(&RawBytes::from( | ||
[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88].to_vec(), | ||
)) | ||
.unwrap() | ||
.into(), | ||
}, | ||
], | ||
}); | ||
util::invoke_contract(&mut rt, &contract_params); | ||
|
||
rt.verify(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we really merging with all these crazy patches?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These particular ones were already there, but I do NOT wish to merge with ref-fvm patches. Those will be removed once filecoin-project/ref-fvm#1049 goes in and we make a release.