Resumable EVM and heap-allocated callstack#9360
Conversation
|
@tomusdrw let's review this together by the end of this week :) |
debris
left a comment
There was a problem hiding this comment.
lgtm, just a few minor grumbles. Would be good to get another pair of eyes to look at it
| Ok(InstructionResult::Ok) | ||
| }, | ||
| Err(trap) => { | ||
| Ok(InstructionResult::Trap(trap)) |
There was a problem hiding this comment.
why is Err turned into Ok here?
There was a problem hiding this comment.
If ext.create returns Err, it can only be a trap. (That also means that if trap parameter is passed false, Err case is impossible.) So in here if we want to trap the interpreter, we can directly match this.
We really have three cases for the exec_instruction return type:
vm::Result::Errwhich indicates some errors happened.vm::Result::Ok(Instruction::Result::Trap(..))which indicates that a trap in the EVM happened.vm::Result::Ok(..)which indicates some other instruction results.
Putting Trap to error and wrap vm::Result::Err can also work, but it means that we need to replicate a lot more From/Into impls to support ? syntax in this function, and I think it may be a little bit overkill.
| Ok(InstructionResult::Ok) | ||
| }, | ||
| Err(trap) => { | ||
| Ok(InstructionResult::Trap(trap)) |
There was a problem hiding this comment.
why is Err turned into Ok here? I just don't understand the flow :)
There was a problem hiding this comment.
Looks like the same case as #9360 (comment)
| Ok(res) | ||
| }, | ||
| CallCreateExecutiveKind::ResumeCreate(..) => | ||
| panic!("Resumable as create, but called resume_call"), |
There was a problem hiding this comment.
is there any way we can avoid those panics?
There was a problem hiding this comment.
We can create traits similar to what we do for vm:
pub trait Exec: Send {
fn exec(self: Box<Self>, state: &mut State<B>, ...) -> ExecutiveTrapResult<GasLeft>;
}
pub trait ResumeCall: Send {
fn resume_call(self: Box<Self>, state: &mut State<B>, ...) -> Box<Exec>;
}
pub trait ResumeCreate: Send {
fn resume_create(self: Box<Self>, state: &mut State<B>, ...) -> Box<Exec>;
}
However, they would need to use traits objects and dynamic dispatch, which is a small amount of runtime overhead. I think it may be a good idea to only use those when in the future we want to expose Executive to be some sort of public API.
| trace_address: self.index_stack.clone(), | ||
| subtraces: self.sublen_stack.last().cloned().unwrap_or(0), | ||
| action: Action::Create(Create::from(params.clone())), | ||
| result: Res::Create(CreateResult { |
There was a problem hiding this comment.
maybe it's worth introducing a third variant? or redesigning the implementation to avoid dummy data creation?
There was a problem hiding this comment.
I think this may be okay. U256::zero(), Vec::new(), Address::default() all would just zero out the fields, and wouldn't allocate anything on the heap.
- From performance's perspective, it wouldn't make any difference if we do dummy data creation or use
Option, they would both just zeroing out the fields. - From clarity's perspective, this dummy data creation is restraint in
ExecutiveTracerand doesn't add any complexity outside of it.
|
Could I get a 2nd review here @fckt @andresilva :) |
| return; | ||
| } | ||
|
|
||
| let vecindex = self.vecindex_stack.pop().expect("prepare/done_trace are not balanced"); |
There was a problem hiding this comment.
would be nice to write why they are always balanced
(and many of those below)
|
🎉 |
Mostly fixes #6744. All basic things for this PR are fixed now.
Exec, which starts a VM execution, and can return a resumable trap,ResumeCall/ResumeCreateto resume execution from call or create traps. Those are accessed using trait objects.Externalities::call/createnow has an additional parametertrap. Iftrapistrue(the case for EVM), then this function would return a trap. Iftrapisfalse(the case for Wasm), then it will directly execute the context.call/createcalls inExecutiveto use a separate structCallCreateExecutive. This is the main struct that handles all resumes. One can useCallCreateExecutive::exec/resume_call/resume_createto get the resume behavior, or useCallCreateExecutive::consumeto get the non-resume behavior.Things to be done:
TODOproof messages.crossbeamagain. In rare cases we can still exceeds current call stack limit (if too many Wasm contexts are stacked).Provide a compile feature flag to enable wasm trap. With pause PR merged to wasmi, that's doable.(This PR is already getting big, let's do this in another PR.)