Meta transactions, Oil, and Karma megathread

Thanks for collecting this into a megathread @pipermerriam !

If someone could help me, I’m trying to understand what breaks in the case of Oil/Karma and ‘meta-transactions’

By my understanding there are different patterns that can be used for ‘meta-transactions’, and in the context of oil/Karma breakage, we’re talking about one genre, the kind that monitor gas remaining and/or make assumptions about gas. ( Native meta-transactions are another breed that I think wouldn’t be affected )

Here’s an example contract that might fit the bill:

(and for some more context, an article that walks through it)

In this contract (or, IDK, in a toy contract that has the properties that matter), what goes wrong when oil/karma is applied, and (if necessary) the gas schedule is changed by some threshold?

I think it would help me understand oil/karma better to understand the failure condition(s) in greater detail.

1 Like

I am considering the case where contract A, the relayer, wants to call contract B, and does it by invoking opcode CALL with the hard-cap (GAS-CAP) on the gas which is forwarded.
The semantics of CALL is such that if the execution of B runs out of the GAS-CAP, the call will still return to A and A will have enough gas left to do its things, and not revert the entire transaction.

Oil may break this if the invocation of B by the relayer A hits the “out-of-oil” exception before it hits “out-of-gas” exception. And because oil cannot be forwarded and has just one single pool, the semantics of “out-of-oil” must inevitably mean that the entire transaction fails, and none of the state changes made before the invocation A=>B apply, and effectively the relayer just wasted ETH on gas/oil.

However, this breakage can be avoided, if the relayer A ensures that the invocation never hits “out-of-oil” before “out-of-gas”. And the trick is to figure out the upper bound of “how much oil can the invocation theoretically spend before it hits the limit of GAS-CAP”. If the relayer then provides (in the gas-limit of the entire transaction, not in the CALL opcode) the amount of oil/gas which is greater or equal than that upper bound, it can guarantee that “out-of-oil” will never happen inside the invocation A=>B.

In the version 1.1 of Oil proposal, we talked about naive and more sophisticated way of calculating the upper bound. We shall try out a sophisticated method (based on taint analysis) to demonstrate that the upper bounds are normally going to be tight enough for this technique to be practical.

1 Like

In-protocol meta-transactions are definitely looking like a better and better idea; saying that you can have meta-transactions but you can only safely use them up to 2 million gas (or whatever 10 million divided by the maximum OIL/GAS ratio is) is going to cause problems (eg. as far as I can tell a STARK-based Tornado Cash-like system would already cost more than 2m gas).

If we have in-protocol meta-transactions (which is certainly a reasonable idea to look into), then we don’t have any significant remaining use cases for untrusted calls, and we can just implement UNGAS, right?

It does seem like maintaining the ability to have untrusted calls is in the long run more trouble than it’s worth.

2 Likes

I agree with that part

That would also be saying that if we start charging for witnesses, these calls that go over 2 million gas routinely won’t be viable (because they are likely the ones that generate large witnesses), and relayers won’t be tasked to execute them. Therefore, I do not see this as a deterrent. What I do see is that relative cost of computation vs consensus over large strings of data (tx inputs, witnesses) will readjust and things like SNARKs will probably be OK if they are mostly computationally intensive.

  1. The breakage that can occur when opcodes are repriced

Many of the discussions around opcode repricing breaking contracts are around parent calling child, but it’s important to note that there are many other scenarios that don’t involve cross-contract calls that could be problematic when it comes to opcode repricing.

Two examples:

  1. Function call costing 3m gas now costing 12m gas. If that functions is deemed safe because it only takes 3m gas, repricing opcodes or a decrease in block gas limit could render that function impossible to execute. The 3m gas is one example, but even a call using 1m gas or less could be made to exceed block gas limit with a few opcode repricing.

  2. Hypothetical Optimistic Rollup: To make sure txs on ORU chains aren’t too complex (so ORU nodes can keep syncing and validating the ORU chain), you need to monitor gas as well. A simple way of doing it is with fraud proofs where users include a gas limit to their signed message, which can be used on-chain to prove that a transaction cost more than the expected value. A change in opcode re-pricing could mean that what used to be valid ORU txs could be made invalid after the fork, allowing the slashing of very old transactions.

There are of course more examples and I believe only thinking of situations involving cross contract calls is not addressing the core issue that re-pricing opcodes can always lead to breaking changes. All contracts currently in existence make an implicit assumption that their functions will always be executable within a block and opcode repricing will always threaten this.

If we want to support opcode repricing, I believe the only viable solution is unfortunately education and tools. Perhaps a fork of ganache could allow devs to run your unit tests with variable opcode prices, from a 0.1 to a 50x scaling factor or whatever we believe is a “reasonable range of opcode repricing”. Making tools like this a “standard” in dev toolkits would mean that developers could have some guarantees that their contracts will work as expected even if opcode are repriced wildly.

Elastic block gas limit like with can EIP-1559 help, but there is always an upper bound that be problematic for some contracts after opcodes are repriced.

2 Likes

If any opcode ever costs more oil than it does in gas, then the child will be able to cause a reversion that propagates through the parent call frame.
This would break meta transactions.

I think this statement is a bit to sweeping. I’d like to pick apart meta-transactions a bit.

A Meta-tx has two parties:

  1. The party that produced the meta-tx, let’s call him the signer.
  2. The party that creates an ethereum transactions, and pays the actual gas. Let’s call him the relayer.

In a meta-tx, the relayer executes the meta-tx, and during the course of execution, some reward is (typically) sent to the relayer. This typically happens in a trustless setting: relayer invokes a meta-tx scheduler which picks a metatx for execution, executes the metatx and afterwards pays a reward for the relayer in some asset.

As I see it, there are two main ways that meta-tx can be broken:

  1. The signer has his transaction executed in a way that makes it fail, but the relayer still gets the reward.
  2. The relayer spends money on a metatx, but is given no reward.

Now, let’s consider the two proposals (oil/karma vs @vbuterin’s counter proposal).

Counter-proposal

The counter-proposal introduces rules for how the tx sender (that is; the relayer) can modify gas-forwarding rules further down in the call stack. This means that actions performed by the relayer can modify the execution flow of the meta-tx.

In short: the possible breakage from Counter-Proposal means that meta-txs can be broken in way 1).

Oil/Karma

With oil/karma, it’s possible that the entire execution is reverted, costing money for relayer. Breakage of type 2).

Analysis

I’d argue that type-1 breakage is worse thann type-2 breakage. A type-2 breakage introduces a level of risk, for the active participants of the meta-tx game. If you want to execute a meta-tx, you would now have to take into account the possibility that the execution may fail. If we see meta-txs as a market, it could be argued that these risks should be organically handled by an efficient market.

  • Does ‘rotten’ meta-txs cause relaying to happen at a loss? Then relayers will stop relaying unless the reward is sufficiently high to offset the risk.
  • A relayer can also (try to) analyse meta-transactions, and estimate which ones are likely to cause a loss. Different relayers may do this differently, but I don’t see that this would be impossible.

As for type-1 breakage, the signer submits his meta-tx. After that has happened, potential attackers have unlimited time to analyze if they can cause the transaction to fail. If the meta-tx is a regular occurrence (like alarm-clock), it might be possible to totally drain the underlying asset while not actually performing anything useful work at all for the signer.

Lastly

Another object to raising gas costs in general, is that the block gas limit can rise above where it’s possible to execute transactions. I see ‘balancing gas costs in accordance with resource expenditure’ as a difficult and important problem to solve. The problem with large transactions vs max gas seems to me to be a lot simpler problem, which can be solved in a number of ways:

  • EIP-1559 is one way,
  • We could have ‘superblocks’ e…g every 10K blocks, which are substantially higher in max gas.
2 Likes

I agree that type 1) is the main problem, as I mentioned here : Counter-proposal to oil/karma: per-account gas limits

But I don’t see how the oil/karma proposal would be immune from 1) the problem is that current CALL opcode only enforce a max gas. They protect the parent not the child.

EIP-1930 is a very simple solution to that problem and using it for metatx does not make them opcode pricing dependent as the gas is specified by the signed message.

The oil/karma proposal meanst that raising the gas limits will not cause any new problems – because if it causes breakage, the entire tx is reverted (costing the sender the fee, but no other sideeffects). So in that sense it is immune.

However, if there is type-1 breakage today, due to gas mechanics, they don’t get “fixed” by oil/karma.

I wasn’t sure which one of those things you were referring to.

1 Like

Ah, yes, you right, and oil/karma proposal would even allow existing contract that relies on computing the gas cost of the call prior to calling (see solution : check done before the call in the rationale section of EIP-1930) to ensure the child receive the correct amount of gas to continue working.

Such solution has actually been adopted by gnosis safe to fix their metatx safety bug
their fix can be seen here

But apart from this, which would be more elegantly (and a lot more simply) solved with EIP-1930, what benefit does it bring to contract developer to be able to use hard coded gas value ?

If we follow the following

And add EIP-1930 for meta-tx support, then I do not see what oil/karma brings to the table ?

Well, I think we may be two very having different perspectives. I believe your goal is to improve the situation for meta-txs, in general, and view the oil/karma/counter as different proposals in that direction.

However, oil/karma/something are proposals to solve a different problem[1], not related to meta-txs. This thread is about possible side-effects with oil/karma/something, and whether one such side-effect is that they “destroy” metatxs, or not.

[1] The problem of how we can modify gas costs, e.g. in the context of rebalancing opcodes and/or paying for witness size. Without screwing layer-2 over

Could you point to documentation where opcode/witness size gas pricing need to be hardcoded in the layer 2 smart contracts ? Because if there exist mechanism to make such smart contract independent of opcode pricing then we would not need oi/karma.

Any prior art on this subject? It seems like adding a sponsor field to transactions for a relayer’s signature would do the trick. They lose a little control in terms of cancelability, since the transaction’s nonce would not be tied to their account, but I’m not sure how important that is to maintain.

I’m not sure this is ever possible however. So long as there is a block gas limit, increasing the cost of opcodes can always render some functions unusable if they now cost more than the gas limit.

I have my own proposal for meta-transactions (that we are developing for RSK), that works when combining a sponsor signatory with the rich transactions proposal.


The idea is that the payload to be executed in the context of the origin account has the following logic:

  1. Call to the token contract Transfer() to pay an agreed amount of tokens to the Sponsor
  2. A call to self (ORIGIN) to the method ExecuteUserAction(args), where args are user-defined from the real transaction (DESTINATION, arguments).
  3. When the method ExecuteUserAction(args) is executed, the arguments are extracted and a CALL is performed.

The recursive encapsulation ensures that the callee receives the call from the ORIGIN EOA, and not from any other sender. It also ensures that even if the DESTINATION rises a OOG or REVERTs, the exception is caught and the payment in tokens is never aborted.

But I first would like to ask a question for you:

Why do you want to increase the cost of an opcode X?

Isn’t that the same as decreasing the cost of all opcodes/constants except X and decreasing the block gas limit ?

That is a backward-compatible way of increasing the cost of an opcode. I wouldn’t care much if the numbers are rounded like 21K or instead they are like 19732. Once you do it once, you won’t feel the pain ever again.:slight_smile:

Interesting! I’ll read up more about that!

Almost, but not quite, because of intrinsic constants such as 2300 (and possibly other hardcoded gas assumptions on layer 2). Let’s say we want to target SLOAD as an example. And lower all others. If we let the 2300 remain in-place as is, then we’ve suddenly changed “what can be done on 2300”. And now it would be possible to perform CALL on those 2300.

Further, if we want to do any substantial change, we’d have to also use fractional gas costs, which is another pretty large change.

Section 9 of this paper: https://bitcoin.org/bitcoin.pdf.

1 Like

I’m going to get another write-up on this done later this week but right now, the most promising approach I’m seeing is:

  1. Separate “gas payer” from “transaction sender”
    • Exact mechanism still TBD
      • Extend transaction format to allow optional “gas payer” signature. This change must be backwards compatible since there is a LOT of existing tooling and infrastructure bound to the current format.
    • Aimed at replacing the existing meta-transaction mechanism. Would need to be rolled out ASAP to give time for migration from old “deprecated” approach.
    • Need to investigate whether we also need in-protocol atomic batching of transactions.
  2. UNGAS:
    • Breaks current meta-transaction mechanism
    • Allows us broader latitude to reprice operations (specifically for witness gas accounting)
    • Will need to do analysis to find what contracts will break and address those breakages individually as we become aware of them

It appears that with these two changes we get the freedom to reprice opcodes for witness gas accounting and better mechanism for implementing meta-transactions. Next steps are 1) validating that there isn’t something missing here and 2) starting work on formalizing the specifications for both of these changes with specific thought towards getting the decoupling of gas-payer/txn-sender into a fork asap since there will be a need for time between rolling that out and rolling out UNGAS to give people time to upgrade.

1 Like

Some more notes on separating “gas payer” from “sender”

  • Contracts need to be able to identify who gas payer is.
    • We can possible use ORIGIN for this. Need to investigate if there are any problems with using this approach.
  • Contract needs a mechanism for paying the gas payer for the gas used.
    • UNGAS will make the current approach of measuring no longer viable.
    • GAS_LIMIT would be under the control of the sender so contracts can just use GAS_LIMITto compute payment amount which will tend to overpay the gas payer but this seems ok and can be mitigated with good gas estimations.

Assuming the current transaction parameters are:

txn = [to, nonce, data, value, gas_price, gas_limit, v, r, s]

A transaction with a separate gas payer transaction would include another nonce, v, r, s which signs the whole txn (including the sender’s signature).

txn = [to, nonce, data, value, gas_price, gas_limit, v, r, s, gp_nonce, gp_v, gp_r, gp_s]

This naive approach has some problems.

  • One could monitor the transaction pool, find these transactions, strip out the gas payer signatures and replace them with their own (Sniping?)
  • One could strip off the gas payer signature and just broadcast the base transaction parameters (which should be a valid transaction)

So it seems we need a mechanism to provide the following assurances…

  • Ensure that the base transaction parameters cannot be stripped out and broadcast in isolation
  • Explore what guarantees we need for the sender and the gas payer
    • There are definitely situations where sender should be able to sign without knowing gas payer…
    • Do we need a way for sender to designate gas payer…
    • Are there cases where gas payer needs exclusivity (outside of being explicitely specified by the sender).

Hello everyone,

I am happy to see interest on enabling gas abstraction in protocol level, thanks for the effort of everyone, as this is a very important improvement for the Ethereum ecossystem and UX.

I am quite unsure if I am missing something, because for me it looks like all these meta transactions proposals are over complexing something that can be solved in a more generic and simple matter. I would not be covering the issues with repricing or the proposal of Oil/Karma in this post, so this contribution is solely related to the gas abstraction effort.

I’ve been researching on the gas abstraction since Jan 2018 (see details here), and since the beginning the idea I had in mind was to propose a soft fork of the miners to allow them to accept gas relayed transactions.

There are mainly 3 types of gas relayed transactions I identified:

  • Contract operated in ERC20 Token Contract (as in ERC 865)
  • Contract operated in Singleton Contract (as in Tornado Cash, or Alarm Clock)
  • Account operated (as in ERC 1077)

As one of the authors of ERC1077, together with @alexvandesande, I plan to update 1077 to bring a standard for all those types, but currently 1077 only defines for account contracts. I already updated 1077 to be more generic and less memory intensive, by using less parameters in the method signature.

With such standardization, I started two more EIPs to fully integrate the 1077 within the protocol:

  • Gas Abstraction (EIP 2473) which proposes a soft fork for miners/validators to be able to recognize and directly include this transactions
  • Coinbase calls (EIP 2474) to reduce the processing cost of gas abstracted calls.

In regards of 2473, the idea is that, similar how to regular transactions work, the validators can validate the outcome of transactions and decide if they are worth including, and if so, include them with a gas price 0. There is no need of protocol upgrade for implementing this, is a soft fork which only changes how miners/validators pick and include transactions in blocks. This would solve the “relayer network” dilemma, because now miners would be able to directly accept any tokens they choose.
Whisper would be used to communicate such transactions, but each token address would have it’s own topic.
This also would solve the problems of relayers networks for Tornado Cash, as said here.

2474 improves the gas abstraction, making such calls safer and cheaper, as they won’t have anymore the regular “outer transaction”, instead, validator would be able to call limited functions in contracts directly. This limited functions would simply be calls which don’t access msg.sender, tx.origin, neither gas.price. where such calls could render a invalid jump or a hardcoded value.

All those EIPs are a current work in progress which everyone here is invited to participate and become authors, by simply providing any improvement to those proposals. Currently I let them aside as no one showed interested in them, and instead are trying to create different solutions I don’t understand/agree, but if there is interest I can get back in that research.

Anyway, I would be happy with any solution that allow users to seamlessly transfer ERC20 tokens paying gas in the token itself (or other token they choose), so if maybe I am in the wrong direction here I can just let you solve this issue and focus on other research.

Please, let me know if I am missing something and why the solutions I propose are not good enough, or what are the weakness of them, I’ll be happy in researching solutions on this topic, and/or updating them to the contexts of EIP-1559/Oil/Karma. Thank you all for the hard work!

Agree with your points above.

Regarding this, it seems simpler to not include the sponsor’s nonce. Because the base transaction has a unique hash (protected by the signer’s nonce) the sponsor’s signature on that transaction will only ever be able to be accepted once. This removes the need to construct a more complicated dependency structure between the the signer and sponsor nonces.

For example, suppose we have the following contents in the mempool and a new incoming transaction:

Visually, it’s clear that TRANSACTION TUPLE 4 depends on TRANSACTION TUPLE 3, which in turn depends on STANDARD TX 1 – the transaction that would be replaced by 4 since they both have nonce of 1 for account A, and therefore it is invalid. This adds a lot of complexity to the existing rules in the mempool. I believe the best two approaches for validating an incoming transaction with dual nonce dependencies are to either i) do a cycle check on the dependency DAG or ii) maintain a state diff for each tx. Neither scenario seems ideal.

When the sponsor’s nonce is not included, transactions can be accepted, rejected, and substituted in a much more similar manner as they are today. The sponsor’s balance acts like an amorphous accumulator that has no ordered dependencies to maintain – just ensure it’s balance is greater than value + gas_price * gas_limit of the transaction it’s accepting and when a transaction they’re sponsoring is demoted, replenish their balance with the demoted amount.

The main casualty I see from dropping the nonce is that the sponsor won’t easily be able to cancel their transaction by issuing a new one with the same nonce and much higher gas price.

the validators can validate the outcome of transactions and decide if they are worth including, and if so, include them with a gas price 0

A few thoughts on this:

  • validating a transaction at the consensus layer would cost O(n) since it could pay out in tokens at the very end of execution. Maintaining O(1) cost of validation should be a goal at the protocol layer. This could be remedied via a configurable parameter set by miner.
  • it’s not clear what standard set of tokens will be accepted by miners – some esoteric token may only be accepted by a few miners and therefore the latency could be very high. Even if a small number of relayers accepted an esoteric token, there would be no degradation in latency.
  • this seems to necessitate a lot more infrastructure for miners: rate negotiation endpoints, watch lists of tokens and conversion rates, validator logic watching the miner’s accounts on various tokens before and after execution, etc.
  • dapps who want to onboard users may be willing to altruistically sponsor their users’ transactions, but this scheme seems to only cater more to a 1:1 relationship between the miner and signer

This is an interesting alternative though and it should not be overlooked. I think as we flesh out all proposals more we’ll better understand the advantages and disadvantages of each.

4 Likes