Skip to content

Commit 534792d

Browse files
Polishing and testing StorageResourceConstraints
1 parent 29131cb commit 534792d

File tree

5 files changed

+239
-169
lines changed

5 files changed

+239
-169
lines changed

arbos/arbosState/arbosstate.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func OpenArbosState(stateDB vm.StateDB, burner burn.Burner) (*ArbosState, error)
8181
if arbosVersion == 0 {
8282
return nil, ErrUninitializedArbOS
8383
}
84-
constraintsStorage := backingStorage.OpenStorageBackedBytes(constraintsSubspace)
84+
constraintsBytes := backingStorage.OpenStorageBackedBytes(constraintsSubspace)
8585

8686
return &ArbosState{
8787
arbosVersion: arbosVersion,
@@ -90,7 +90,7 @@ func OpenArbosState(stateDB vm.StateDB, burner burn.Burner) (*ArbosState, error)
9090
networkFeeAccount: backingStorage.OpenStorageBackedAddress(uint64(networkFeeAccountOffset)),
9191
l1PricingState: l1pricing.OpenL1PricingState(backingStorage.OpenCachedSubStorage(l1PricingSubspace), arbosVersion),
9292
l2PricingState: l2pricing.OpenL2PricingState(backingStorage.OpenCachedSubStorage(l2PricingSubspace)),
93-
resourceConstraints: constraints.NewStorageResourceConstraints(&constraintsStorage),
93+
resourceConstraints: constraints.NewStorageResourceConstraints(&constraintsBytes),
9494
retryableState: retryables.OpenRetryableState(backingStorage.OpenCachedSubStorage(retryablesSubspace), stateDB),
9595
addressTable: addressTable.Open(backingStorage.OpenCachedSubStorage(addressTableSubspace)),
9696
chainOwners: addressSet.OpenAddressSet(backingStorage.OpenCachedSubStorage(chainOwnerSubspace)),
@@ -530,6 +530,10 @@ func (state *ArbosState) Blockhashes() *blockhash.Blockhashes {
530530
return state.blockhashes
531531
}
532532

533+
func (state *ArbosState) ResourceConstraints() *constraints.StorageResourceConstraints {
534+
return state.resourceConstraints
535+
}
536+
533537
func (state *ArbosState) NetworkFeeAccount() (common.Address, error) {
534538
return state.networkFeeAccount.Get()
535539
}

arbos/constraints/constraints.go

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

77
import (
8-
"io"
98
"iter"
109

1110
"github.com/ethereum/go-ethereum/arbitrum/multigas"
12-
"github.com/ethereum/go-ethereum/rlp"
1311

1412
"github.com/offchainlabs/nitro/util/arbmath"
1513
)
@@ -153,102 +151,3 @@ func (rc *ResourceConstraints) All() iter.Seq[*ResourceConstraint] {
153151
}
154152
}
155153
}
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: 0 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -222,69 +222,3 @@ 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-
}

arbos/constraints/storage.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2025, Offchain Labs, Inc.
2+
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md
3+
4+
package constraints
5+
6+
import (
7+
"io"
8+
9+
"github.com/ethereum/go-ethereum/rlp"
10+
)
11+
12+
// storageBytes defines the interface for ArbOS storage.
13+
type storageBytes interface {
14+
Get() ([]byte, error)
15+
Set(val []byte) error
16+
}
17+
18+
// StorageResourceConstraints defines a storage-backed ResourceConstraints.
19+
type StorageResourceConstraints struct {
20+
storage storageBytes
21+
}
22+
23+
// NewStorageResourceConstraints creates a new storage-backed ResourceConstraints.
24+
func NewStorageResourceConstraints(storage storageBytes) *StorageResourceConstraints {
25+
return &StorageResourceConstraints{
26+
storage: storage,
27+
}
28+
}
29+
30+
type resourceConstraintRLP struct {
31+
Resources []ResourceWeight
32+
Period PeriodSecs
33+
TargetPerSec uint64
34+
Backlog uint64
35+
}
36+
37+
// EncodeRLP encodes ResourceConstraint deterministically,
38+
// ensuring the fixed-length weights array is preserved.
39+
func (c *ResourceConstraint) EncodeRLP(w io.Writer) error {
40+
weights := make([]ResourceWeight, len(c.Resources.weights))
41+
copy(weights, c.Resources.weights[:])
42+
return rlp.Encode(w, resourceConstraintRLP{
43+
Resources: weights,
44+
Period: c.Period,
45+
TargetPerSec: c.TargetPerSec,
46+
Backlog: c.Backlog,
47+
})
48+
}
49+
50+
// DecodeRLP decodes ResourceConstraint deterministically,
51+
// padding or truncating the weights slice to the correct array length.
52+
func (c *ResourceConstraint) DecodeRLP(s *rlp.Stream) error {
53+
var raw resourceConstraintRLP
54+
if err := s.Decode(&raw); err != nil {
55+
return err
56+
}
57+
c.Period = raw.Period
58+
c.TargetPerSec = raw.TargetPerSec
59+
c.Backlog = raw.Backlog
60+
61+
for i := range c.Resources.weights {
62+
if i < len(raw.Resources) {
63+
c.Resources.weights[i] = raw.Resources[i]
64+
} else {
65+
c.Resources.weights[i] = 0
66+
}
67+
}
68+
return nil
69+
}
70+
71+
// Load decodes ResourceConstraints from storage using RLP.
72+
// If storage is empty, returns an empty ResourceConstraints.
73+
func (src *StorageResourceConstraints) Load() (*ResourceConstraints, error) {
74+
data, err := src.storage.Get()
75+
if err != nil {
76+
return nil, err
77+
}
78+
if len(data) == 0 {
79+
return NewResourceConstraints(), nil
80+
}
81+
82+
var list []*ResourceConstraint
83+
if err := rlp.DecodeBytes(data, &list); err != nil {
84+
return nil, err
85+
}
86+
87+
rc := NewResourceConstraints()
88+
for _, c := range list {
89+
rc.Set(c.Resources, c.Period, c.TargetPerSec)
90+
ptr := rc.Get(c.Resources, c.Period)
91+
ptr.Backlog = c.Backlog
92+
}
93+
94+
return rc, nil
95+
}
96+
97+
// Write encodes ResourceConstraints into storage using RLP.
98+
func (src *StorageResourceConstraints) Write(rc *ResourceConstraints) error {
99+
var list []*ResourceConstraint
100+
for c := range rc.All() {
101+
list = append(list, c)
102+
}
103+
104+
// If there are no constraints, clear the storage instead of writing 0xC0
105+
if len(list) == 0 {
106+
return src.storage.Set(nil)
107+
}
108+
109+
data, err := rlp.EncodeToBytes(list)
110+
if err != nil {
111+
return err
112+
}
113+
return src.storage.Set(data)
114+
}

0 commit comments

Comments
 (0)