L2 compute gas pricing

We recently (March 4) updated the algorithm that sets the L2 gas price for computation. Here’s a quick summary of what we changed. We’re interested in any suggestions of feedback on this part of the design.

In this post I’ll describe how it worked before the upgrade. The next post in the thread will describe what we changed and why.

The L2 compute price adjusts automatically via a mechanism inspired by Ethereum’s EIP-1559 basefee setting algorithm. The price depends on gas usage compared to a “speed limit” parameter. When usage is above the speed limit over a period of time, the L2 compute price automatically adjusts upward; when usage is below the speed limit, it adjusts downward. There is a floor below which the L2 compute price can’t go.

Before the change, we used a “gas pool” based pricing system. Think of the gas pool as a “gas tank” that can hold up to a certain amount of gas. Compute gas (not including gas charged for storage) used by the system gets subtracted from the gas pool; and every time one second elapses a fixed amount of gas is added to the pool (unless it’s full). The rate of adding gas to the pool is the chain’s “speed limit” which you can think of as a gas limit per second that is enforced on average. If the gas pool gets completely empty, the chain rejects all transactions until the pool gets more gas.

In that pre-change system, each time one second passed, if the gas pool fullness was a fraction f (between 0 and 1), the price would be multiplied by the factor (1 + (1-2f)/120). This would boost the price by 0.8% if the pool was empty, cut it by 0.8% if the pool was full, and leave it unchanged if the pool was half full.

The logic of this scheme is that we wanted to keep gas usage at or below the speed limit on average. And we wanted to allow bursts of traffic that used more than the speed limit, but only for a limited time. If there was a sustained period of usage above the speed limit, first the price would escalate temporarily until usage dipped back below the speed limit. But if the price escalation wasn’t enough to stop the overuse, eventually the gas pool would be completely empty causing a “circuit breaker” to trip and the system would reject all transactions for a period of seconds before re-opening.

Currently the speed limit is 120,000 ArbGas of computation per second, and the gas pool size is 200,000,000 ArbGas. As an example, suppose usage jumps up to 200,000 ArbGas per second (80,000 above the speed limit). Then the gas pool would shrink by 80,000 per second. If this continues for 21 minutes, the gas pool will be 99,200,000 – less than half full – so the L2 compute gas price will start escalating, very slowly at first, and then more rapidly if the pool continues to shrink – but never increasing by more than 0.8% per second. If the usage doesn’t decrease after another 20 minutes, despite the price escalation, the circuit breaker will trip and the system will reject perhaps 30 seconds of transactions before re-opening. But this is very unlikely because the price will have had plenty of time to adjust, and users to respond by reducing their traffic.

This system has worked quite well, but we saw ways to improve it. See the next post for details on that.

6 Likes

The initial compute gas pricing algorithm worked well. But we noticed two issues. First, when a burst of traffic started, the algorithm might take a long time to “notice” the surge and start adjusting the price. In the example above, a surge was going for 21 minutes before the pricing algorithm started adjusting. And second, when the pricing algorithm did respond, it would often overshoot, taking the price higher than needed. Even if usage had dropped, the price would keep increasing until the gas pool got back up to 50% full.

To address both of these issues, we decided to add a momentum term to pricing algorithm. The idea is that rather than using the gas pool fullness as the only indicator of load, we would add a second indicator, which is an estimate of the gas usage rate over the last one minute or so, compared to the speed limit. We would average the two indicators so that each one had equal power to pull the price up or down. That way, if the gas pool was fairly full but shrinking fast, the new “momentum” indicator would pull the price up even while the fullness indicator was pulling it down. Similarly, if the pool was (say) 30% full but refilling rapidly, the momentum indicator would pull the price down even while the pool fullness indicator was pulling it up. The result would be a system that responded faster to sudden surges, and stopped escalating the price if the gas pool was refilling successfully.

We deployed this change on March 4.

Another change we deployed at the same time is to have the gas pool fullness indicator “shoot for” a pool fullness of 80% rather than 50% as it was previously. This again would make the system react a bit faster but more gently to surges.

To see the effect of this, let’s reconsider the example from the previous post, where the gas pool is initially full with low usage, but usage jumps up to 200,000 compute gas per second, still with the current speed limit of 120,000 per second and gas pool of 200,000,000.

After a couple of minutes, the momentum term will be estimating usage of around 1.6 times the speed limit, and the gas pool will be down to 95% full. At this point the momentum term will be pulling the price upward because it sees usage above the speed limit, but the gas pool will be pulling the price down because the pool is above the target of 80% fullness. The algorithm translates the gas pool fullness into a ratio of 0.84 (= min(2.0, 80/95)). The average of the two ratios is 1.22, so the price would be pulled upward on net, by about 0.2% per second.

That seems about right. A surge well above the speed limit, having lasted for a couple of minutes, should induce a mild price increase. If the surge continues, the momentum ratio will stay at 1.6, but the gas pool fullness ratio will creep downward, and the price escalation will get faster, up to a limit of 0.8% per second. If the surge stops, the momentum ratio will drop and the price should start dropping again as the gas pool refills.

Based on our analysis, the new algorithm should be more responsive and should overshoot less.

Does this make sense? What do folks think about this approach?

2 Likes

Thanks for sharing! This makes absolute sense. It’s like adding the derivative term to your previous P-Controller. Now it’s kinda a PD-Controller.

1 Like

After some experience with the pricer described in my previous post–which has worked quite well–we realized we could switch to an even simpler system. So Nitro now uses the exponential pricing mechanism, as Vitalik has described in various posts.

The new system tracks a “gas backlog” which intuitively is how much gas has been used recently beyond the system’s speed limit parameter. When a transaction uses gas, that gas is added to the backlog. Every time one second elapses, the speed limit is subtracted from the backlog, but the backlog never goes below zero.

If the backlog is below a tolerance parameter, the L2 basefee is set to the minimum basefee parameter, which is currently 0.1 gwei in Nitro. If the backlog is above the tolerance, the basefee is set to minimumBaseFee * exp(alpha * (backlog-tolerance)). The constant alpha is set so that the basefee is multiplied by 7/8 if there is no gas usage for 12 seconds–this matches the rate of decrease that EIP-1559 would cause on Ethereum under the same conditions (assuming the post-merge block time of 12 seconds).

There is no maximum imposed on the backlog, nor is there any cutoff where the system will refuse transactions. The pricing mechanism will naturally ensure that the backlog doesn’t get too large.

The use of an exponential might seem scary, but the alpha parameter is quite small, so there won’t be crazy fluctuations under realistic conditions. By way of example, even if gas usage is double the speed limit, the basefee will shift by less than 1% per second.