Skip to content
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

used_gas: how can we provide exact measure of gas used/remaining gas back to the contract? #3

Closed
nagisa opened this issue Jun 15, 2022 · 8 comments

Comments

@nagisa
Copy link
Collaborator

nagisa commented Jun 15, 2022

A problem description needs to be written down here.

@matklad @Ekleog @jakmeier @akhi3030

@nagisa
Copy link
Collaborator Author

nagisa commented Jun 15, 2022

As a brief thought, I believe the only side effects we have or care about are:

  • host function calls, which used_cas is;
  • traps.

So it might not be super onerous to split the metered blocks (in the parlance of our current instrumentation spec) into two at those points.

@matklad
Copy link

matklad commented Jun 15, 2022

Note: I am off for a long weekend now, if I forget to fill this in, please ping me!

My main contributions:

used_gas semantics can either be precise, or approximate. Precise semantics makes aggregrating gas per block more inloved (you need to split basic block in two). I've seen this causing complexity in EVM, I wish we could avoid that.

@jakmeier
Copy link

I can see the following potential use cases:

  1. Calculate how much gas is left for promises.
    • Given X has been attached and Y has been burned, X-Y is left for a promise. Underestimating Y even just a little bit would be a problem, as we would try to attach more than we have left.
  2. Terminate gracefully mid-work shortly before running out of gas. Then continue where it left next time the function gets called again.
  3. Figure out contract reward amount the call generated. For example, to give it back to the user, or split it between creator and developer, or any other financial shenanigans.
  4. Figure out how much a promise will use at most. For example, consider this code.
before := used_gas()
self.infallible()
after := used_gas()
cost := after - before + BASE_COST
promise_action_function_call(A::infallible, attachedGas = cost)
// do more stuff with remaining gas

In this constructed example, if after := used_gas() is overestimated more than before := used_gas(), then cost would be too low and the promise fail. I hope it makes the basic idea clear.

If we want to have an accurate idea of what devs actually care about, perhaps we need input from DevRel.

But regardless, if we can make things cleaner without too much hassle, I would prefer that. Who knows, maybe someday, it will save a DeFi contract from breaking mid transaction that their devs were oh-so-sure will always be atomic.

@matklad
Copy link

matklad commented Jun 22, 2022

Problem statement:

It is natural to formulate gas metering on the per-instruction basis. But implementation can be much faster if it aggregates all the gas spent in a block. Doing this optimization risks leaking per-block nature of gas accounting.

We need to:

  1. Identify the cases where per-block nature of gas counting would be observable in terms of outcomes (that is, we don't necessary care about contract running a bit longer, only about, eg, it spending a bit more gas)
  2. Identify where we care about those cases leaking instrumentation details
  3. Identify whether we can actually avoid leaking such details
  4. Identify whether we can still reasonably spec semantics even if details are leaked.

@matklad
Copy link

matklad commented Jun 22, 2022

My takes are

  1. I think we only need to worry about used_gas (returns different result), failures from the host fns or traps potentially spending mode gas, or host-fns prematurely failing with out-of-gas. Notably, we also have to worry about indirect calls, cause might point to used_gas
  2. I think we don't, in general, care: the result from used_gas isn't really wrong -- you are guaranteed to spend all that gas anyway. In some sense, used_gas returning a lower bound on what you would spend is even more useful.
  3. Potentially? We can say that any observable op splits basic block
  4. Maaaybe? for me, the biggest advantage of the per-instruction semantics is that its easier to spec.

And there's also the case that we can change semantics later via a protocol version. So I'd probably not overthing this, and just do the per-block thing.

@nagisa
Copy link
Collaborator Author

nagisa commented Jun 22, 2022

That’s a nice way to formulate it.

What counts for a “leak” is, sort of, use-case specific. In our case, for example, we don’t care or need the contract memory to be deterministic at the end of execution, so we can afford to collapse gas accounting for a significantly extended set of instructions compared to use-cases which require memory to reflect exactly the correct termination point.

I don’t believe runtime semantics would be affected by such simplifications much if at all, but using the per-instruction-gas model would definitely make it more difficult to write spec tests and an implementation (which might want to handle different modes of operation).

@nagisa
Copy link
Collaborator Author

nagisa commented Jun 22, 2022

I worry that if we spontaneously implement something now, it can be quite difficult to actually change the approach taken for various reasons, and protocol bump is probably the least of those, one of which would be interactions with other sorts of instrumentation.

Another reason we want to get this right from the get go is to not deal with dozen copies of the instrumentation code in the codebase.

@nagisa
Copy link
Collaborator Author

nagisa commented Aug 8, 2022

As per wording, the exact measure of the remaining gas pool is always available. Any attempts to obtain this count should return the exact value.

Unfortunately this still doesn’t help with the use case of “attach all remaining gas to the function call action” as there can be an opaque number of webassembly instructions between the call and prior readout of the remaining gas. That said, my feeling is that this ought to be solved outside of this spec/framework. If this is a desirable operation, a host function ought to be adapted or added to implement this behaviour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants