-
Notifications
You must be signed in to change notification settings - Fork 644
Compute gas price based on multiple constraints #3872
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
base: master
Are you sure you want to change the base?
Changes from 6 commits
9d9414b
2f0979f
1895eb0
765358d
8399d6b
63010bb
32905a2
f09d1d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,11 +4,51 @@ | |
package l2pricing | ||
|
||
import ( | ||
"fmt" | ||
"math/big" | ||
|
||
"github.com/offchainlabs/nitro/arbos/storage" | ||
"github.com/offchainlabs/nitro/util/arbmath" | ||
) | ||
|
||
const ( | ||
gasConstraintTargetOffset uint64 = iota | ||
gasConstraintPeriodOffset | ||
gasConstraintDivisorOffset | ||
gasConstraintBacklogOffset | ||
) | ||
|
||
// GasConstraint tries to keep the gas backlog under the target (per second) for the given period. | ||
// The divisor is based on the target and period, and can be computed by computeConstraintDivisor. | ||
type GasConstraint struct { | ||
target storage.StorageBackedUint64 | ||
period storage.StorageBackedUint64 | ||
divisor storage.StorageBackedUint64 | ||
backlog storage.StorageBackedUint64 | ||
} | ||
|
||
func OpenGasConstraint(storage *storage.Storage) *GasConstraint { | ||
return &GasConstraint{ | ||
target: storage.OpenStorageBackedUint64(gasConstraintTargetOffset), | ||
period: storage.OpenStorageBackedUint64(gasConstraintPeriodOffset), | ||
divisor: storage.OpenStorageBackedUint64(gasConstraintDivisorOffset), | ||
backlog: storage.OpenStorageBackedUint64(gasConstraintBacklogOffset), | ||
} | ||
} | ||
|
||
func (c *GasConstraint) Clear() error { | ||
if err := c.target.Clear(); err != nil { | ||
return err | ||
} | ||
if err := c.divisor.Clear(); err != nil { | ||
return err | ||
} | ||
if err := c.backlog.Clear(); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
type L2PricingState struct { | ||
storage *storage.Storage | ||
speedLimitPerSecond storage.StorageBackedUint64 | ||
|
@@ -19,6 +59,7 @@ type L2PricingState struct { | |
pricingInertia storage.StorageBackedUint64 | ||
backlogTolerance storage.StorageBackedUint64 | ||
perTxGasLimit storage.StorageBackedUint64 | ||
constraints *storage.SubStorageVector | ||
} | ||
|
||
const ( | ||
|
@@ -32,6 +73,8 @@ const ( | |
perTxGasLimitOffset | ||
) | ||
|
||
var constraintsKey []byte = []byte{0} | ||
|
||
const GethBlockGasLimit = 1 << 50 | ||
|
||
func InitializeL2PricingState(sto *storage.Storage) error { | ||
|
@@ -55,6 +98,7 @@ func OpenL2PricingState(sto *storage.Storage) *L2PricingState { | |
pricingInertia: sto.OpenStorageBackedUint64(pricingInertiaOffset), | ||
backlogTolerance: sto.OpenStorageBackedUint64(backlogToleranceOffset), | ||
perTxGasLimit: sto.OpenStorageBackedUint64(perTxGasLimitOffset), | ||
constraints: storage.OpenSubStorageVector(sto.OpenSubStorage(constraintsKey)), | ||
} | ||
} | ||
|
||
|
@@ -128,3 +172,69 @@ func (ps *L2PricingState) SetBacklogTolerance(val uint64) error { | |
func (ps *L2PricingState) Restrict(err error) { | ||
ps.storage.Burner().Restrict(err) | ||
} | ||
|
||
func (ps *L2PricingState) SetConstraintsFromLegacy() error { | ||
if err := ps.ClearConstraints(); err != nil { | ||
return err | ||
} | ||
target, err := ps.SpeedLimitPerSecond() | ||
if err != nil { | ||
return err | ||
} | ||
inertia, err := ps.PricingInertia() | ||
if err != nil { | ||
return err | ||
} | ||
// Make an approximation of the period based on the inertia | ||
periodSqrt := inertia / ConstraintDivisorMultiplier | ||
period := arbmath.SaturatingUMul(periodSqrt, periodSqrt) | ||
if period == 0 { | ||
// Ensure the period is at least 1 | ||
period = 1 | ||
} | ||
return ps.AddConstraint(target, period) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably this is the right place to transfer legacy backlog. I can do it in my PR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, my idea is to call SetConstraintsFromLegacy when upgrading to Arbos50 here. |
||
} | ||
|
||
func (ps *L2PricingState) AddConstraint(target uint64, period uint64) error { | ||
subStorage, err := ps.constraints.Push() | ||
if err != nil { | ||
return fmt.Errorf("failed to push constraint: %w", err) | ||
} | ||
constraint := OpenGasConstraint(subStorage) | ||
if err := constraint.target.Set(target); err != nil { | ||
return fmt.Errorf("failed to set target: %w", err) | ||
} | ||
if err := constraint.period.Set(period); err != nil { | ||
return fmt.Errorf("failed to set period: %w", err) | ||
} | ||
if err := constraint.divisor.Set(computeConstraintDivisor(target, period)); err != nil { | ||
return fmt.Errorf("failed to set divisor: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
func (ps *L2PricingState) ConstraintsLength() (uint64, error) { | ||
return ps.constraints.Length() | ||
} | ||
|
||
func (ps *L2PricingState) OpenConstraintAt(i uint64) *GasConstraint { | ||
return OpenGasConstraint(ps.constraints.At(i)) | ||
} | ||
|
||
func (ps *L2PricingState) ClearConstraints() error { | ||
length, err := ps.ConstraintsLength() | ||
if err != nil { | ||
return err | ||
} | ||
for range length { | ||
subStorage, err := ps.constraints.Pop() | ||
if err != nil { | ||
return err | ||
} | ||
constraint := OpenGasConstraint(subStorage) | ||
if err := constraint.Clear(); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// Copyright 2025, Offchain Labs, Inc. | ||
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md | ||
|
||
package l2pricing | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestComputeConstraintDivisor(t *testing.T) { | ||
cases := []struct { | ||
target uint64 | ||
period uint64 | ||
want uint64 | ||
}{ | ||
{ | ||
target: 7_000_000, | ||
period: 12, | ||
want: 630_000_000, | ||
}, | ||
{ | ||
target: 15_000_000, | ||
period: 86_400, // one day | ||
want: 131_850_000_000, | ||
}, | ||
} | ||
for _, test := range cases { | ||
got := computeConstraintDivisor(test.target, test.period) | ||
if got != test.want { | ||
t.Errorf("wrong result for target=%v period=%v: got %v, want %v", | ||
test.target, test.period, got, test.want) | ||
} | ||
} | ||
} | ||
|
||
func TestCompareLecayPricingModelWithMultiConstraints(t *testing.T) { | ||
pricing := PricingForTest(t) | ||
|
||
// In this test, we don't check for storage set errors because they won't happen and they | ||
// are not the focus of the test. | ||
|
||
// Set the innertia to a value that is divisible by 30 to negate the rounding error | ||
_ = pricing.SetPricingInertia(120) | ||
|
||
// Set the tolerance to zero because this doesn't exist in the new model | ||
_ = pricing.SetBacklogTolerance(0) | ||
|
||
// Initialize with a single constraint based on the legacy model | ||
_ = pricing.SetConstraintsFromLegacy() | ||
|
||
// Compare the basefee for both models with different backlogs | ||
for backlogShift := range uint64(16) { | ||
for timePassed := range uint64(5) { | ||
backlog := uint64(1 << backlogShift) | ||
|
||
_ = pricing.gasBacklog.Set(backlog) | ||
pricing.UpdatePricingModel(nil, timePassed, false) | ||
legacyPrice, _ := pricing.baseFeeWei.Get() | ||
|
||
constraint := pricing.OpenConstraintAt(0) | ||
_ = constraint.backlog.Set(backlog) | ||
pricing.UpdatePricingModelMultiConstraints(timePassed) | ||
multiPrice, _ := pricing.baseFeeWei.Get() | ||
|
||
if multiPrice.Cmp(legacyPrice) != 0 { | ||
t.Errorf("wrong result: backlog=%v, timePassed=%v, multiPrice=%v, legacyPrice=%v", | ||
backlog, timePassed, multiPrice, legacyPrice) | ||
} | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.