Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions arbos/l2pricing/l2pricing.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,46 @@
package l2pricing

import (
"fmt"
"math/big"

"github.com/offchainlabs/nitro/arbos/storage"
)

const (
gasConstraintTargetOffset uint64 = iota
gasConstraintPeriodOffset
gasConstraintBacklogOffset
)

// GasConstraint tries to keep the gas backlog under the target (per second) for the given period.
type GasConstraint struct {
target storage.StorageBackedUint64
period storage.StorageBackedUint64
backlog storage.StorageBackedUint64
}

func OpenGasConstraint(storage *storage.Storage) *GasConstraint {
return &GasConstraint{
target: storage.OpenStorageBackedUint64(gasConstraintTargetOffset),
period: storage.OpenStorageBackedUint64(gasConstraintPeriodOffset),
backlog: storage.OpenStorageBackedUint64(gasConstraintBacklogOffset),
}
}

func (c *GasConstraint) Clear() error {
if err := c.target.Clear(); err != nil {
return err
}
if err := c.period.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
Expand All @@ -19,6 +54,7 @@ type L2PricingState struct {
pricingInertia storage.StorageBackedUint64
backlogTolerance storage.StorageBackedUint64
perTxGasLimit storage.StorageBackedUint64
constraints *storage.SubStorageVector
}

const (
Expand All @@ -32,6 +68,8 @@ const (
perTxGasLimitOffset
)

var constraintsKey []byte = []byte{0}

const GethBlockGasLimit = 1 << 50

func InitializeL2PricingState(sto *storage.Storage) error {
Expand All @@ -55,6 +93,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)),
}
}

Expand Down Expand Up @@ -128,3 +167,47 @@ func (ps *L2PricingState) SetBacklogTolerance(val uint64) error {
func (ps *L2PricingState) Restrict(err error) {
ps.storage.Burner().Restrict(err)
}

func (ps *L2PricingState) AddConstraint(target uint64, period uint64, backlog 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.backlog.Set(backlog); err != nil {
return fmt.Errorf("failed to set backlog: %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
}
42 changes: 42 additions & 0 deletions arbos/l2pricing/l2pricing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,48 @@ func getSpeedLimit(t *testing.T, pricing *L2PricingState) uint64 {
return value
}

func getConstraintsLength(t *testing.T, pricing *L2PricingState) uint64 {
length, err := pricing.ConstraintsLength()
Require(t, err)
return length
}

func TestGasConstraints(t *testing.T) {
pricing := PricingForTest(t)
if got := getConstraintsLength(t, pricing); got != 0 {
t.Fatalf("wrong number of constraints: got %v want 0", got)
}
const n uint64 = 10
for i := range n {
Require(t, pricing.AddConstraint(100*i+1, 100*i+2, 100*i+3))
}
if got := getConstraintsLength(t, pricing); got != n {
t.Fatalf("wrong number of constraints: got %v want %v", got, n)
}
for i := range n {
constraint := pricing.OpenConstraintAt(i)
target, err := constraint.target.Get()
Require(t, err)
if want := 100*i + 1; target != want {
t.Errorf("wrong target: got %v, want %v", target, want)
}
period, err := constraint.period.Get()
Require(t, err)
if want := 100*i + 2; period != want {
t.Errorf("wrong period: got %v, want %v", period, want)
}
backlog, err := constraint.backlog.Get()
Require(t, err)
if want := 100*i + 3; backlog != want {
t.Errorf("wrong backlog: got %v, want %v", backlog, want)
}
}
Require(t, pricing.ClearConstraints())
if got := getConstraintsLength(t, pricing); got != 0 {
t.Fatalf("wrong number of constraints: got %v want 0", got)
}
}

func Require(t *testing.T, err error, printables ...interface{}) {
t.Helper()
testhelpers.RequireImpl(t, err, printables...)
Expand Down
74 changes: 74 additions & 0 deletions arbos/storage/vector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2025, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md

package storage

import (
"encoding/binary"
"errors"
)

const subStorageVectorLengthOffset uint64 = 0

// SubStorageVector is a storage space that contains a vector of sub-storages.
// It keeps track of the number of sub-storages and It is possible to push and pop them.
type SubStorageVector struct {
storage *Storage
length StorageBackedUint64
}

// OpenSubStorageVector creates a SubStorageVector in given the root storage.
func OpenSubStorageVector(sto *Storage) *SubStorageVector {
return &SubStorageVector{
sto.WithoutCache(),
sto.OpenStorageBackedUint64(subStorageVectorLengthOffset),
}
}

// Length returns the number of sub-storages.
func (v *SubStorageVector) Length() (uint64, error) {
length, err := v.length.Get()
if err != nil {
return 0, err
}
return length, err
}

// Push adds a new sub-storage at the end of the vector and return it.
func (v *SubStorageVector) Push() (*Storage, error) {
length, err := v.length.Get()
if err != nil {
return nil, err
}
id := binary.BigEndian.AppendUint64(nil, length)
subStorage := v.storage.OpenSubStorage(id)
if err := v.length.Set(length + 1); err != nil {
return nil, err
}
return subStorage, nil
}

// Pop removes the last sub-storage from the end of the vector and return it.
func (v *SubStorageVector) Pop() (*Storage, error) {
length, err := v.length.Get()
if err != nil {
return nil, err
}
if length == 0 {
return nil, errors.New("sub-storage vector: can't pop empty")
}
id := binary.BigEndian.AppendUint64(nil, length-1)
subStorage := v.storage.OpenSubStorage(id)
if err := v.length.Set(length - 1); err != nil {
return nil, err
}
return subStorage, nil
}

// At returns the substorage at the given index.
// NOTE: This function does not verify out-of-bounds.
func (v *SubStorageVector) At(i uint64) *Storage {
id := binary.BigEndian.AppendUint64(nil, i)
subStorage := v.storage.OpenSubStorage(id)
return subStorage
}
81 changes: 81 additions & 0 deletions arbos/vector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md

package arbos

import (
"testing"

"github.com/offchainlabs/nitro/arbos/arbosState"
"github.com/offchainlabs/nitro/arbos/storage"
"github.com/offchainlabs/nitro/arbos/util"
)

// This file contains tests for the storage submodule, but it needs to be in the top-level module to
// avoid cross dependencies.

func TestSubStorageVector(t *testing.T) {
state, statedb := arbosState.NewArbosMemoryBackedArbOSState()
sto := state.BackingStorage().OpenCachedSubStorage([]byte{})
vec := storage.OpenSubStorageVector(sto)

stateBefore := statedb.IntermediateRoot(false)

getLength := func() uint64 {
length, err := vec.Length()
Require(t, err)
return length
}

// Check it starts empty
if got, want := getLength(), uint64(0); got != want {
t.Fatalf("wrong vector length: got %v, want %v", got, want)
}

// Adds n elements
const n = uint64(100)
for i := range n {
subStorage, err := vec.Push()
Require(t, err)
err = subStorage.SetByUint64(0, util.UintToHash(i))
Require(t, err)
}

// Check the length with n elements
if got, want := getLength(), uint64(100); got != want {
t.Fatalf("wrong vector length: got %v, want %v", got, want)
}

// Check each element
for i := range n {
subStorage := vec.At(i)
got, err := subStorage.GetByUint64(0)
Require(t, err)
want := util.UintToHash(i)
if got != want {
t.Errorf("wrong sub-storage: got %v, want %v", got, want)
}
}

// Pop each element and clear storage
for i := range n {
subStorage, err := vec.Pop()
Require(t, err)
got, err := subStorage.GetByUint64(0)
Require(t, err)
want := util.UintToHash(n - i - 1)
if got != want {
t.Errorf("wrong sub-storage: got %v, want %v", got, want)
}
err = subStorage.ClearByUint64(0)
Require(t, err)
}

// Check it ends empty
if got, want := getLength(), uint64(0); got != want {
t.Fatalf("wrong vector length: got %v, want %v", got, want)
}
if stateBefore != statedb.IntermediateRoot(false) {
Fail(t, "state is not clear")
}
}
Loading