Skip to content

Commit eec914d

Browse files
Add resource constraints to ArbOS storage
1 parent aaad87b commit eec914d

File tree

3 files changed

+173
-0
lines changed

3 files changed

+173
-0
lines changed

arbos/arbosState/arbosstate.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/offchainlabs/nitro/arbos/arbostypes"
2626
"github.com/offchainlabs/nitro/arbos/blockhash"
2727
"github.com/offchainlabs/nitro/arbos/burn"
28+
"github.com/offchainlabs/nitro/arbos/constraints"
2829
"github.com/offchainlabs/nitro/arbos/features"
2930
"github.com/offchainlabs/nitro/arbos/l1pricing"
3031
"github.com/offchainlabs/nitro/arbos/l2pricing"
@@ -49,6 +50,7 @@ type ArbosState struct {
4950
networkFeeAccount storage.StorageBackedAddress
5051
l1PricingState *l1pricing.L1PricingState
5152
l2PricingState *l2pricing.L2PricingState
53+
resourceConstraints *constraints.StorageResourceConstraints
5254
retryableState *retryables.RetryableState
5355
addressTable *addressTable.AddressTable
5456
chainOwners *addressSet.AddressSet
@@ -79,13 +81,16 @@ func OpenArbosState(stateDB vm.StateDB, burner burn.Burner) (*ArbosState, error)
7981
if arbosVersion == 0 {
8082
return nil, ErrUninitializedArbOS
8183
}
84+
constraintsStorage := backingStorage.OpenStorageBackedBytes(constraintsSubspace)
85+
8286
return &ArbosState{
8387
arbosVersion: arbosVersion,
8488
upgradeVersion: backingStorage.OpenStorageBackedUint64(uint64(upgradeVersionOffset)),
8589
upgradeTimestamp: backingStorage.OpenStorageBackedUint64(uint64(upgradeTimestampOffset)),
8690
networkFeeAccount: backingStorage.OpenStorageBackedAddress(uint64(networkFeeAccountOffset)),
8791
l1PricingState: l1pricing.OpenL1PricingState(backingStorage.OpenCachedSubStorage(l1PricingSubspace), arbosVersion),
8892
l2PricingState: l2pricing.OpenL2PricingState(backingStorage.OpenCachedSubStorage(l2PricingSubspace)),
93+
resourceConstraints: constraints.NewStorageResourceConstraints(&constraintsStorage),
8994
retryableState: retryables.OpenRetryableState(backingStorage.OpenCachedSubStorage(retryablesSubspace), stateDB),
9095
addressTable: addressTable.Open(backingStorage.OpenCachedSubStorage(addressTableSubspace)),
9196
chainOwners: addressSet.OpenAddressSet(backingStorage.OpenCachedSubStorage(chainOwnerSubspace)),
@@ -187,6 +192,7 @@ var (
187192
programsSubspace SubspaceID = []byte{8}
188193
featuresSubspace SubspaceID = []byte{9}
189194
nativeTokenOwnerSubspace SubspaceID = []byte{10}
195+
constraintsSubspace SubspaceID = []byte{11}
190196
)
191197

192198
var PrecompileMinArbOSVersions = make(map[common.Address]uint64)

arbos/constraints/constraints.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
package constraints
66

77
import (
8+
"io"
89
"iter"
910

1011
"github.com/ethereum/go-ethereum/arbitrum/multigas"
12+
"github.com/ethereum/go-ethereum/rlp"
1113

1214
"github.com/offchainlabs/nitro/util/arbmath"
1315
)
@@ -151,3 +153,102 @@ func (rc *ResourceConstraints) All() iter.Seq[*ResourceConstraint] {
151153
}
152154
}
153155
}
156+
157+
// storageBytes defines the interface for ArbOS storage.
158+
type storageBytes interface {
159+
Get() ([]byte, error)
160+
Set(val []byte) error
161+
}
162+
163+
// StorageResourceConstraints defines a storage-backed ResourceConstraints.
164+
type StorageResourceConstraints struct {
165+
storage storageBytes
166+
}
167+
168+
// NewStorageResourceConstraints creates a new storage-backed ResourceConstraints.
169+
func NewStorageResourceConstraints(storage storageBytes) *StorageResourceConstraints {
170+
return &StorageResourceConstraints{
171+
storage: storage,
172+
}
173+
}
174+
175+
type resourceConstraintRLP struct {
176+
Resources []ResourceWeight
177+
Period PeriodSecs
178+
TargetPerSec uint64
179+
Backlog uint64
180+
}
181+
182+
// EncodeRLP encodes ResourceConstraint deterministically,
183+
// ensuring the fixed-length weights array is preserved.
184+
func (c *ResourceConstraint) EncodeRLP(w io.Writer) error {
185+
weights := make([]ResourceWeight, len(c.Resources.weights))
186+
copy(weights, c.Resources.weights[:])
187+
return rlp.Encode(w, resourceConstraintRLP{
188+
Resources: weights,
189+
Period: c.Period,
190+
TargetPerSec: c.TargetPerSec,
191+
Backlog: c.Backlog,
192+
})
193+
}
194+
195+
// DecodeRLP decodes ResourceConstraint deterministically,
196+
// padding or truncating the weights slice to the correct array length.
197+
func (c *ResourceConstraint) DecodeRLP(s *rlp.Stream) error {
198+
var raw resourceConstraintRLP
199+
if err := s.Decode(&raw); err != nil {
200+
return err
201+
}
202+
c.Period = raw.Period
203+
c.TargetPerSec = raw.TargetPerSec
204+
c.Backlog = raw.Backlog
205+
206+
for i := range c.Resources.weights {
207+
if i < len(raw.Resources) {
208+
c.Resources.weights[i] = raw.Resources[i]
209+
} else {
210+
c.Resources.weights[i] = 0
211+
}
212+
}
213+
return nil
214+
}
215+
216+
// Load decodes ResourceConstraints from storage using RLP.
217+
// If storage is empty, returns an empty ResourceConstraints.
218+
func (src *StorageResourceConstraints) Load() (*ResourceConstraints, error) {
219+
data, err := src.storage.Get()
220+
if err != nil {
221+
return nil, err
222+
}
223+
if len(data) == 0 {
224+
return NewResourceConstraints(), nil
225+
}
226+
227+
var list []*ResourceConstraint
228+
if err := rlp.DecodeBytes(data, &list); err != nil {
229+
return nil, err
230+
}
231+
232+
rc := NewResourceConstraints()
233+
for _, c := range list {
234+
rc.Set(c.Resources, c.Period, c.TargetPerSec)
235+
ptr := rc.Get(c.Resources, c.Period)
236+
ptr.Backlog = c.Backlog
237+
}
238+
239+
return rc, nil
240+
}
241+
242+
// Write encodes ResourceConstraints into storage using RLP.
243+
func (src *StorageResourceConstraints) Write(rc *ResourceConstraints) error {
244+
var list []*ResourceConstraint
245+
for c := range rc.All() {
246+
list = append(list, c)
247+
}
248+
249+
data, err := rlp.EncodeToBytes(list)
250+
if err != nil {
251+
return err
252+
}
253+
return src.storage.Set(data)
254+
}

arbos/constraints/constraints_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,69 @@ func TestAllResourceConstraints(t *testing.T) {
222222
require.True(t, found1)
223223
require.True(t, found2)
224224
}
225+
226+
// mockStorageBytes is a simple in-memory implementation of storageBytes.
227+
type mockStorageBytes struct {
228+
buf []byte
229+
}
230+
231+
func (m *mockStorageBytes) Get() ([]byte, error) { return m.buf, nil }
232+
func (m *mockStorageBytes) Set(val []byte) error { m.buf = val; return nil }
233+
234+
func TestStorageResourceConstraintsRLPRoundTrip(t *testing.T) {
235+
rc := NewResourceConstraints()
236+
237+
res1 := EmptyResourceSet().
238+
WithResource(multigas.ResourceKindComputation, 1).
239+
WithResource(multigas.ResourceKindHistoryGrowth, 2)
240+
rc.Set(res1, PeriodSecs(12), 7_000_000)
241+
rc.Get(res1, PeriodSecs(12)).Backlog = 42
242+
243+
res2 := EmptyResourceSet().
244+
WithResource(multigas.ResourceKindWasmComputation, 3)
245+
rc.Set(res2, PeriodSecs(60), 3_500_000)
246+
rc.Get(res2, PeriodSecs(60)).Backlog = 99
247+
248+
store := &mockStorageBytes{}
249+
src := NewStorageResourceConstraints(store)
250+
require.NoError(t, src.Write(rc))
251+
require.Greater(t, len(store.buf), 0, "storage should contain RLP bytes after Write")
252+
253+
loaded, err := src.Load()
254+
require.NoError(t, err, "Load() failed")
255+
256+
require.Equal(t, len(rc.constraints), len(loaded.constraints), "constraint count mismatch")
257+
258+
for orig := range rc.All() {
259+
found := false
260+
for got := range loaded.All() {
261+
if got.Period != orig.Period ||
262+
got.TargetPerSec != orig.TargetPerSec ||
263+
got.Backlog != orig.Backlog {
264+
continue
265+
}
266+
eq := true
267+
for i := range orig.Resources.weights {
268+
if orig.Resources.weights[i] != got.Resources.weights[i] {
269+
eq = false
270+
break
271+
}
272+
}
273+
if eq {
274+
found = true
275+
break
276+
}
277+
}
278+
require.Truef(t, found, "missing constraint after Load (period=%d target=%d)", orig.Period, orig.TargetPerSec)
279+
}
280+
}
281+
282+
func TestStorageResourceConstraintsEmptyLoad(t *testing.T) {
283+
store := &mockStorageBytes{}
284+
src := NewStorageResourceConstraints(store)
285+
286+
loaded, err := src.Load()
287+
require.NoError(t, err, "Load() failed")
288+
require.NotNil(t, loaded, "Load() returned nil ResourceConstraints")
289+
require.Empty(t, loaded.constraints, "Loaded ResourceConstraints should be empty")
290+
}

0 commit comments

Comments
 (0)