-
Notifications
You must be signed in to change notification settings - Fork 160
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
Refactor events and advice injectors #1457
Comments
I think the initial example here does a good job of making the technical issue clear, but is maybe not that great at demonstrating why it's an issue in practice. I've thrown together a basic example that I think illustrates why not accounting for advice decorators in the MAST root is problematic:
Compiling with just
Doing the same with Doing the same with
So these two procedures have the same MAST root, but very different semantics:
From an execution trace point of view, the decorators are invisible, so these two programs (as shown above) can have identical traces, depending on the contents of the advice stack - but trying to infer anything from the equivalence of those traces is pretty meaningless, since the programs are prima facie not the same (in one, we know precisely what data we expect to be asserting against, in the latter, it is effectively a roll of the dice). So what does it mean for the traces to be identical? Is that valuable on its own? Anyway, I wanted to add a richer example here, since I think the first question one would have is "what's the practical impact of this?". EDIT: I should also note, if compiling the example code above, if you leave both procedure definitions in the source file, you have no idea which one you'll actually get, a fun roll of the dice! |
Ah that's interesting, I hadn't considered that other custom decorators might be desirable, but that makes a ton of sense (e.g. for implementing things like It seems to me that supporting generic user-defined decorators (including optional syntax in MASM) is not particularly difficult, but would require telling the VM to load one or more dynamic libraries containing the decorator implementations at startup. Each such decorator (say, by implementing a Using Probably not a critical issue at this juncture though. |
@igamigo discovered one other situation where we face a similar problem as described in the original post: error codes for
I think we should go with the second approach because error codes are conceptually similar to debugging info. Basically, even if we strip all error codes from Separately, I think the first approach will be quite non-trivial to implement because there are quite a few instructions which use error codes. |
Thinking about this a bit more, I think event source collisions would be all too frequent. Even if all libraries properly choose an event source at random, relying on a probability of collision of Emit { event_source: u32, event_id: u32 } where The MASM
(e.g. where As for registering event handlers, I see the processor having an extra // (pseudo-code)
trait EventHandler {
fn on_event(event_id: u32, advice_provider: &mut AdviceProvider);
}
impl Processor {
// would fail if we already have a handler for that source id
fn register_event_handler(source_id: SourceId, handler: Box<dyn EventHandler>) -> Result<(), ExecutionError> { ... }
} I would indeed love to simplify all the |
Agreed that
Overall, my initial choice of (if we go with
I like this approach - and this is roughly what I was thinking about for the 3rd step in the "implementation plan" from the original post. We'll need to work through some details here, as for example, |
Background
The trigger for this issue is the situation we faced after migration to the
MastForest
-based execution is illustrated by the following code:In the above, both
foo
andbar
have the same MAST root because they only difference between them is the presence of theadv_push_mapval
decorator and presence of decorators does not affect the program MAST.The reason why decorators do not affect the MAST is because from the trace (and verifier) standpoint, the two procedures are actually identical. However, from the executor/prover standpoint they are clearly not. So, we have the following issue:
The second point is problematic because we currently rely on MAST roots to identify procedures during execution (e.g., all calls are done by MAST roots).
Broadly, there are two ways to solve this:
Related problem
There is another problem that is somewhat related to this. There is a subset of decorators we call "advice injectors" which provide custom functionality for specific instructions/procedures - but in a way are hard-coded in the VM. For example, we have an
adv.push_smtpeek
decorator which exists in the processor solely to support several SMT-related procedures inmiden-stdlib
(this is a kind of backward coupling).However, for anything other than
mdien-stdlib
we cannot add decorators to the VM (as we cannot predict what kind of decorators the people will need downstream). To address this gap, we have theemit.<event_id>
decorator on which we rely inmiden-base
. But this also has its own issues as it is not clear how multiple 3rd-party libraries can use custom events at the same time.So, to summarize, we have advice injectors and events that do kind of similar things but for different contexts, and we also have some implied dependencies between downstream components (i.e.,
miden-stdlib
) and the VM itself.Potentail solution
I think we can address the above issues holistically. Taking a step back, we really have 3 classes of operations in the VM:
trace
,debug
, andAsmOp
decorators.Only the first two have semantic meaning: regular operations are meaningful both for the prover and the verifier; advice injectors and events are meaningful for the prover. The 3rd class of operations can be safely stripped from a program and it should not effect anything about how the program is executed or verified.
So, my proposal would be to change advice injectors and events into a kind of "Noop" operations which affect the program hash, but do not change the state of the stack or memory. This is basically approach 2 described above: we include these decorators into the MAST root computation and so if a decorator changes, MAST root of a program would change as well.
Below is a description of how this can be done.
Emit
operationFirst, we would add an
Emit
operation to the set of operations which can be executed by the processor. This operation could look like so:It would be the second operation which carries an immediate value (Push(Felt) being the other such operation).
The processor treats
Emit
operation very similar to thePush
operation, except nothing gets pushed onto the stack. This way, from the trace standpoint,Emit
is effectively aNoop
but the immediate value is included in the MAST root computation.The processor would also call the Host::on_event() handler passing the inner
u32
value asevent_id
. This mechanism would replace our current Event decorator (we are basically changingEvent
decorator intoEmit
operation).However, we can push this a step further and replace all advice injectors with the
Emit
instruction. Specifically, we can interpretevent_id
as follows:The sources could have the following meaning:
0
- events emitted internally by the VM. These would be used for decorators used in assembly instructions.1
- events emitted by themiden-stdlib
.2
- events emitted by themiden-lib
(i.e., for the rollup).3..
- 3rd party libraries.Events with domain
0
will be handled by the VM internally andHost::on_event()
handler would not be called for them. For everything else,Host::on_event()
handler will forward the events to the appropriate library.Effects on the
Host
andAdviceProvider
traitsThe above means that we should be able to simplify the
Host
andAdviceProvider
traits. Specifically, we no longer need to make all advice injectors "overridable" - and so, we can internalize a lot of the currently public logic (AdviceProvider
trait could be simplified quite a bit).We could also get rid of the concept of
AdviceExtractor
, and maybe alsoAdviceInjector
(we should be able to handle purely via event handlers). But we may need to expose the advice provider via theHost
interface. Maybe something like this:Host:on_event()
would actually need to be modified to look as follows:Impact on constraints
Since the
Emit
operation would need to be treated similarly to thePush
operation, the constraints for it would need to be similar. This means thatEmit
needs to be a very high-degree operation (e.g., constraints for it are of degree 5). Unfortunately, we currently don't have any more available slots for such operations.However, since now we have two similar operations, we should be able to move them both into the "high-degree" operation category, and use their leading bits to define a degree 4 flag - e.g., something like
op_with_immediate
(we do something similar for control flow flags and shift-left flags). If this works, the net impact will be that we'll free up one slot in the "very high-degree" operation bucket.Impact on libraries
We'll need to come up with a mechanism of how attach custom event handlers to libraries. There are probably many ways to do this. For example, handlers could be defined in specific library structs (e.g.,
StdLibrary
could define a set of event handlers), or we could have some kind of registry at the VM level. I'm leaving figuring out the concrete details here for the future.Implementation plan
Implementing the above would be quite a bit of work - so, I think we should do it stages which could look as follows:
Event
decorator withEmit
operation (this would also involve updating the constraints).Host
/AdviceProvider
refactoring).The text was updated successfully, but these errors were encountered: