Read-only Mode #487
Replies: 6 comments 2 replies
-
I think this proposal strikes a good balance between being effectively read only without imposing the tax of gating on every mutation call. It's simple to implement, and makes it easy for the FEVM runtime to enforce the EVM contract by gating locally at the opcode level. It's also extensible with more strictness at the kernel level in the future (e.g. failing on attempted writes). My only gripe is that when I see the term "read-only" as a dev, I assume that write operations are simply statically unavailable, or would fail immediately. I would suggest to name this flag "ephemeral" or something similar to make it clear that writes are allowed (and paid for!), but just not saved. |
Beta Was this translation helpful? Give feedback.
-
Noting prior discussion of some similar ideas: filecoin-project/ref-fvm#725 |
Beta Was this translation helpful? Give feedback.
-
Thanks for this. ClarificationDoes "revert" mean:
From your example, I understand that the flag is not automatically transitive. What this means in practise is that, despite being in a readonly context, when B calls C, C is not in a readonly context and can (temporarily) persist changes such that when B calls C again it can observe those changes. I think this makes sense thus far. What's not specified is whether A can observe changes in C's state after B returns. (1) above would mean it could, (2) means it couldn't. I think (1) is insufficient to be useful, and is also really confusing to A, and the mechanism should be (2). EventsWhat happens to events (#484) in a readonly context? With the semantics of "no observable changes" I would suggest that events should be suppressed if the actor was also unable to persist any state change. In the example above, this means B's events too, even though it wasn't in readonly context. If events are still externalised, systems relying on them to drive indexing, UI etc will be unable to rely on any event data, and forced to inspect state to determine deltas (or lack thereof) every time. Edit: I note that EVM LOG opcodes revert when in static context (EIP-214) DXI'm not really sold on preferring the revert mechanism over forbidding writes when they happen. Firstly, this check will be of trivial cost in the scheme of processing a write, so I don't think performance is a meaningful consideration. You note that actors could implement this themselves, but in what cases would writing without checking not constitute a bug? The actor is behaving differently to how the author apparently intended. An actor that fails to consider whether it could be in a readonly context should probably fail fast, both in dev to alert the programmer but also, conservatively, in prod. Edit: I note that EVM SSTORE reverts in static context (EIP-214). But one possible reason is... Impact on reachabilityWe should clarify any interactions with block reachability when passing links as capabilities between actors. One reason to write blocks is to pass them to another actor. This is a valid need even in read-only mode. However such blocks might have never been linked to the caller's state anyway (tho they might be linked to the callee's state, but that should be reverted later with (2) above). The same applies for return data (I think). If an actor writes blocks that it intends to link from its return value, those blocks need to survive for the caller to see them. Basically, this read-only thing is distinct from state-tree reversion. If this means we can't deny writes as they happen, an alternate implementation is to forbid EVMFrom your brief description, I've inferred that you intend to implement STATICCALL by, in the EVM actor, passing the readonly flag in response to that opcode, and also passing it on transitively at any call if the context indicates readonly. The "many actors" implementation of FEVM means that the STATICCALL caller cannot rely on the callee passing on the context like this. Someone could write a new EVM actor that's the same except it doesn't pass on the readonly, and then deploy many contracts that use this. The caller can't rely on the VM to set this context in all callees. With mechanism (2) above, I don't think this matters. But to avoid any possible confusion, I would suggest that the FVM actor not pass on the flag transitively, lest anyone be misled into thinking that it does anything effective. However if the correctness/safety of EVM actors depends on them being able to observe whether any caller has used STATICCALL, then you have a problem here. A way out would be to push this behaviour into the VM, enforcing the transitivity including in the B->C call in your example. ExtensionI don't think this is worthwhile. A similar thing can be achieved by invoking self and aborting from that invocation. Alternative - transactionsI don't think this is worth implementing either, since it can be simulated by actors. An actor can call another, inspect the result, and then decide whether it wants the callee to persist changes by inserting a call to itself in between, which can inspect the result and chose to abort, to be caught by the "actual" caller. Since this can be done, I don't think we should do anything at the VM level until we observe actor authors so desperate for these semantics that they're using that pattern. |
Beta Was this translation helpful? Give feedback.
-
For completeness and to prevent confusion, there's also a distinct "no self-modification" flag proposed for top-level messages. This is not transitive, but would provide a good heuristic for mempools to support holding multiple messages from a single abstract account at once. It's different to this proposal, though.
|
Beta Was this translation helpful? Give feedback.
-
Note: now that I think about it, almost all of this proposal could be implemented in userspace now that we can return values on exit.
However, the downsides are:
|
Beta Was this translation helpful? Give feedback.
-
Can you confirm the behaviour when native token value is sent alongside a read-only send? I would expect that the value is transferred to the receiver, and probably that they are unable to send it anywhere else during the call. Possibly I could see that they can send the value on elsewhere (but also transitively read-only), but that could be confusing. |
Beta Was this translation helpful? Give feedback.
-
Actors need to be able to call into other actors without modifying state (e.g., for querying). Specifically, we need to allow the caller to specify that state shouldn't change.
Requirements
Proposal
send
syscall.vm::context
).read-only
flag set.For example, given:
readonly
bit set.In FEVM, we'd pass
readonly
tosend
if:STATICCALL
.Extensions
We could extend this by allowing the user to call some
vm::set_readonly()
method to enter read-only mode. All prior changes would be kept (e.g., value transfers), but any future changes would be reverted on return (even if the return is successful).Alternatives
transaction without calling). This would allow a user to decide whether or not they want to keep
the results of some call. However, we (STATICCALL) needs a way to guarantee to the callee that the
results will not be committed.
Beta Was this translation helpful? Give feedback.
All reactions