Skip to content

Commit dd90204

Browse files
authored
op-program: Update the host config to support multiple L2s (#13719)
* op-program: Introduce L2Sources to combine info for multiple L2s. * op-program: Update the host config to support multiple L2s The CLI flags still only support a single L2. * op-program: Update interop bootstrap to load multiple chain configs
1 parent e89248a commit dd90204

File tree

20 files changed

+736
-129
lines changed

20 files changed

+736
-129
lines changed

op-e2e/system/proofs/system_fpp_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ func testFaultProofProgramScenario(t *testing.T, ctx context.Context, sys *e2esy
287287
preimageDir := t.TempDir()
288288
fppConfig := oppconf.NewConfig(sys.RollupConfig, sys.L2GenesisCfg.Config, s.L1Head, s.L2Head, s.L2OutputRoot, common.Hash(s.L2Claim), s.L2ClaimBlockNumber)
289289
fppConfig.L1URL = sys.NodeEndpoint("l1").RPC()
290-
fppConfig.L2URL = sys.NodeEndpoint("sequencer").RPC()
290+
fppConfig.L2URLs = []string{sys.NodeEndpoint("sequencer").RPC()}
291291
fppConfig.L1BeaconURL = sys.L1BeaconEndpoint().RestHTTP()
292292
fppConfig.DataDir = preimageDir
293293
if s.Detached {
@@ -314,7 +314,7 @@ func testFaultProofProgramScenario(t *testing.T, ctx context.Context, sys *e2esy
314314
t.Log("Running fault proof in offline mode")
315315
// Should be able to rerun in offline mode using the pre-fetched images
316316
fppConfig.L1URL = ""
317-
fppConfig.L2URL = ""
317+
fppConfig.L2URLs = nil
318318
err = opp.FaultProofProgramWithDefaultPrefecher(ctx, log, fppConfig)
319319
require.NoError(t, err)
320320

op-program/client/boot/boot.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,11 @@ import (
66
"math"
77

88
"github.com/ethereum-optimism/optimism/op-node/rollup"
9-
preimage "github.com/ethereum-optimism/optimism/op-preimage"
109
"github.com/ethereum-optimism/optimism/op-program/chainconfig"
1110
"github.com/ethereum/go-ethereum/common"
1211
"github.com/ethereum/go-ethereum/params"
1312
)
1413

15-
const (
16-
L1HeadLocalIndex preimage.LocalIndexKey = iota + 1
17-
L2OutputRootLocalIndex
18-
L2ClaimLocalIndex
19-
L2ClaimBlockNumberLocalIndex
20-
L2ChainIDLocalIndex
21-
22-
// These local keys are only used for custom chains
23-
L2ChainConfigLocalIndex
24-
RollupConfigLocalIndex
25-
)
26-
2714
// CustomChainIDIndicator is used to detect when the program should load custom chain configuration
2815
const CustomChainIDIndicator = uint64(math.MaxUint64)
2916

@@ -38,10 +25,6 @@ type BootInfo struct {
3825
RollupConfig *rollup.Config
3926
}
4027

41-
type oracleClient interface {
42-
Get(key preimage.Key) []byte
43-
}
44-
4528
type BootstrapClient struct {
4629
r oracleClient
4730
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package boot
2+
3+
import (
4+
"encoding/binary"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
9+
"github.com/ethereum-optimism/optimism/op-node/rollup"
10+
"github.com/ethereum-optimism/optimism/op-program/chainconfig"
11+
"github.com/ethereum/go-ethereum/common"
12+
"github.com/ethereum/go-ethereum/params"
13+
)
14+
15+
var (
16+
ErrUnknownChainID = errors.New("unknown chain id")
17+
)
18+
19+
type BootInfoInterop struct {
20+
Configs ConfigSource
21+
22+
L1Head common.Hash
23+
AgreedPrestate common.Hash
24+
Claim common.Hash
25+
ClaimTimestamp uint64
26+
}
27+
28+
type ConfigSource interface {
29+
RollupConfig(chainID uint64) (*rollup.Config, error)
30+
ChainConfig(chainID uint64) (*params.ChainConfig, error)
31+
}
32+
type OracleConfigSource struct {
33+
oracle oracleClient
34+
35+
customConfigsLoaded bool
36+
37+
l2ChainConfigs map[uint64]*params.ChainConfig
38+
rollupConfigs map[uint64]*rollup.Config
39+
}
40+
41+
func (c *OracleConfigSource) RollupConfig(chainID uint64) (*rollup.Config, error) {
42+
if cfg, ok := c.rollupConfigs[chainID]; ok {
43+
return cfg, nil
44+
}
45+
cfg, err := chainconfig.RollupConfigByChainID(chainID)
46+
if !c.customConfigsLoaded && err != nil {
47+
c.loadCustomConfigs()
48+
if cfg, ok := c.rollupConfigs[chainID]; !ok {
49+
return nil, fmt.Errorf("%w: %v", ErrUnknownChainID, chainID)
50+
} else {
51+
return cfg, nil
52+
}
53+
} else if err != nil {
54+
return nil, err
55+
}
56+
c.rollupConfigs[chainID] = cfg
57+
return cfg, nil
58+
}
59+
60+
func (c *OracleConfigSource) ChainConfig(chainID uint64) (*params.ChainConfig, error) {
61+
if cfg, ok := c.l2ChainConfigs[chainID]; ok {
62+
return cfg, nil
63+
}
64+
cfg, err := chainconfig.ChainConfigByChainID(chainID)
65+
if !c.customConfigsLoaded && err != nil {
66+
c.loadCustomConfigs()
67+
if cfg, ok := c.l2ChainConfigs[chainID]; !ok {
68+
return nil, fmt.Errorf("%w: %v", ErrUnknownChainID, chainID)
69+
} else {
70+
return cfg, nil
71+
}
72+
} else if err != nil {
73+
return nil, err
74+
}
75+
c.l2ChainConfigs[chainID] = cfg
76+
return cfg, nil
77+
}
78+
79+
func (c *OracleConfigSource) loadCustomConfigs() {
80+
var rollupConfigs []*rollup.Config
81+
err := json.Unmarshal(c.oracle.Get(RollupConfigLocalIndex), &rollupConfigs)
82+
if err != nil {
83+
panic("failed to bootstrap rollup configs")
84+
}
85+
for _, config := range rollupConfigs {
86+
c.rollupConfigs[config.L2ChainID.Uint64()] = config
87+
}
88+
89+
var chainConfigs []*params.ChainConfig
90+
err = json.Unmarshal(c.oracle.Get(L2ChainConfigLocalIndex), &chainConfigs)
91+
if err != nil {
92+
panic("failed to bootstrap chain configs")
93+
}
94+
for _, config := range chainConfigs {
95+
c.l2ChainConfigs[config.ChainID.Uint64()] = config
96+
}
97+
c.customConfigsLoaded = true
98+
}
99+
100+
func BootstrapInterop(r oracleClient) *BootInfoInterop {
101+
l1Head := common.BytesToHash(r.Get(L1HeadLocalIndex))
102+
agreedPrestate := common.BytesToHash(r.Get(L2OutputRootLocalIndex))
103+
claim := common.BytesToHash(r.Get(L2ClaimLocalIndex))
104+
claimTimestamp := binary.BigEndian.Uint64(r.Get(L2ClaimBlockNumberLocalIndex))
105+
106+
return &BootInfoInterop{
107+
Configs: &OracleConfigSource{
108+
oracle: r,
109+
l2ChainConfigs: make(map[uint64]*params.ChainConfig),
110+
rollupConfigs: make(map[uint64]*rollup.Config),
111+
},
112+
L1Head: l1Head,
113+
AgreedPrestate: agreedPrestate,
114+
Claim: claim,
115+
ClaimTimestamp: claimTimestamp,
116+
}
117+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package boot
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"math/big"
7+
"testing"
8+
9+
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
10+
"github.com/ethereum-optimism/optimism/op-node/rollup"
11+
preimage "github.com/ethereum-optimism/optimism/op-preimage"
12+
"github.com/ethereum-optimism/optimism/op-program/chainconfig"
13+
"github.com/ethereum/go-ethereum/common"
14+
"github.com/ethereum/go-ethereum/params"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
func TestInteropBootstrap_SimpleValues(t *testing.T) {
19+
expected := &BootInfoInterop{
20+
L1Head: common.Hash{0xaa},
21+
AgreedPrestate: common.Hash{0xbb},
22+
Claim: common.Hash{0xcc},
23+
ClaimTimestamp: 49829482,
24+
}
25+
mockOracle := newMockInteropBootstrapOracle(expected, false)
26+
actual := BootstrapInterop(mockOracle)
27+
require.Equal(t, expected.L1Head, actual.L1Head)
28+
require.Equal(t, expected.AgreedPrestate, actual.AgreedPrestate)
29+
require.Equal(t, expected.Claim, actual.Claim)
30+
require.Equal(t, expected.ClaimTimestamp, actual.ClaimTimestamp)
31+
}
32+
33+
func TestInteropBootstrap_RollupConfigBuiltIn(t *testing.T) {
34+
expectedCfg := chaincfg.OPSepolia()
35+
expected := &BootInfoInterop{
36+
L1Head: common.Hash{0xaa},
37+
AgreedPrestate: common.Hash{0xbb},
38+
Claim: common.Hash{0xcc},
39+
ClaimTimestamp: 49829482,
40+
}
41+
mockOracle := newMockInteropBootstrapOracle(expected, false)
42+
actual := BootstrapInterop(mockOracle)
43+
actualCfg, err := actual.Configs.RollupConfig(expectedCfg.L2ChainID.Uint64())
44+
require.NoError(t, err)
45+
require.Equal(t, expectedCfg, actualCfg)
46+
}
47+
48+
func TestInteropBootstrap_RollupConfigCustom(t *testing.T) {
49+
config1 := &rollup.Config{L2ChainID: big.NewInt(1111)}
50+
config2 := &rollup.Config{L2ChainID: big.NewInt(2222)}
51+
source := &BootInfoInterop{
52+
L1Head: common.Hash{0xaa},
53+
AgreedPrestate: common.Hash{0xbb},
54+
Claim: common.Hash{0xcc},
55+
ClaimTimestamp: 49829482,
56+
}
57+
mockOracle := newMockInteropBootstrapOracle(source, true)
58+
mockOracle.rollupCfgs = []*rollup.Config{config1, config2}
59+
actual := BootstrapInterop(mockOracle)
60+
actualCfg, err := actual.Configs.RollupConfig(config1.L2ChainID.Uint64())
61+
require.NoError(t, err)
62+
require.Equal(t, config1, actualCfg)
63+
64+
actualCfg, err = actual.Configs.RollupConfig(config2.L2ChainID.Uint64())
65+
require.NoError(t, err)
66+
require.Equal(t, config2, actualCfg)
67+
}
68+
69+
func TestInteropBootstrap_ChainConfigBuiltIn(t *testing.T) {
70+
expectedCfg := chainconfig.OPSepoliaChainConfig()
71+
expected := &BootInfoInterop{
72+
L1Head: common.Hash{0xaa},
73+
AgreedPrestate: common.Hash{0xbb},
74+
Claim: common.Hash{0xcc},
75+
ClaimTimestamp: 49829482,
76+
}
77+
mockOracle := newMockInteropBootstrapOracle(expected, false)
78+
actual := BootstrapInterop(mockOracle)
79+
actualCfg, err := actual.Configs.ChainConfig(expectedCfg.ChainID.Uint64())
80+
require.NoError(t, err)
81+
require.Equal(t, expectedCfg, actualCfg)
82+
}
83+
84+
func TestInteropBootstrap_ChainConfigCustom(t *testing.T) {
85+
config1 := &params.ChainConfig{ChainID: big.NewInt(1111)}
86+
config2 := &params.ChainConfig{ChainID: big.NewInt(2222)}
87+
expected := &BootInfoInterop{
88+
L1Head: common.Hash{0xaa},
89+
AgreedPrestate: common.Hash{0xbb},
90+
Claim: common.Hash{0xcc},
91+
ClaimTimestamp: 49829482,
92+
}
93+
mockOracle := newMockInteropBootstrapOracle(expected, true)
94+
mockOracle.chainCfgs = []*params.ChainConfig{config1, config2}
95+
actual := BootstrapInterop(mockOracle)
96+
97+
actualCfg, err := actual.Configs.ChainConfig(config1.ChainID.Uint64())
98+
require.NoError(t, err)
99+
require.Equal(t, config1, actualCfg)
100+
101+
actualCfg, err = actual.Configs.ChainConfig(config2.ChainID.Uint64())
102+
require.NoError(t, err)
103+
require.Equal(t, config2, actualCfg)
104+
}
105+
106+
func newMockInteropBootstrapOracle(b *BootInfoInterop, custom bool) *mockInteropBootstrapOracle {
107+
return &mockInteropBootstrapOracle{
108+
mockBoostrapOracle: mockBoostrapOracle{
109+
l1Head: b.L1Head,
110+
l2OutputRoot: b.AgreedPrestate,
111+
l2Claim: b.Claim,
112+
l2ClaimBlockNumber: b.ClaimTimestamp,
113+
},
114+
custom: custom,
115+
}
116+
}
117+
118+
type mockInteropBootstrapOracle struct {
119+
mockBoostrapOracle
120+
rollupCfgs []*rollup.Config
121+
chainCfgs []*params.ChainConfig
122+
custom bool
123+
}
124+
125+
func (o *mockInteropBootstrapOracle) Get(key preimage.Key) []byte {
126+
switch key.PreimageKey() {
127+
case L2ChainConfigLocalIndex.PreimageKey():
128+
if !o.custom {
129+
panic(fmt.Sprintf("unexpected oracle request for preimage key %x", key.PreimageKey()))
130+
}
131+
b, _ := json.Marshal(o.chainCfgs)
132+
return b
133+
case RollupConfigLocalIndex.PreimageKey():
134+
if !o.custom {
135+
panic(fmt.Sprintf("unexpected oracle request for preimage key %x", key.PreimageKey()))
136+
}
137+
b, _ := json.Marshal(o.rollupCfgs)
138+
return b
139+
default:
140+
return o.mockBoostrapOracle.Get(key)
141+
}
142+
}

op-program/client/boot/boot_test.go

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestBootstrapClient(t *testing.T) {
2424
L2ChainConfig: chainconfig.OPSepoliaChainConfig(),
2525
RollupConfig: rollupCfg,
2626
}
27-
mockOracle := &mockBoostrapOracle{bootInfo, false}
27+
mockOracle := newMockPreinteropBootstrapOracle(bootInfo, false)
2828
readBootInfo := NewBootstrapClient(mockOracle).BootInfo()
2929
require.EqualValues(t, bootInfo, readBootInfo)
3030
}
@@ -39,7 +39,7 @@ func TestBootstrapClient_CustomChain(t *testing.T) {
3939
L2ChainConfig: chainconfig.OPSepoliaChainConfig(),
4040
RollupConfig: chaincfg.OPSepolia(),
4141
}
42-
mockOracle := &mockBoostrapOracle{bootInfo, true}
42+
mockOracle := newMockPreinteropBootstrapOracle(bootInfo, true)
4343
readBootInfo := NewBootstrapClient(mockOracle).BootInfo()
4444
require.EqualValues(t, bootInfo, readBootInfo)
4545
}
@@ -52,26 +52,32 @@ func TestBootstrapClient_UnknownChainPanics(t *testing.T) {
5252
L2ClaimBlockNumber: 1,
5353
L2ChainID: uint64(0xdead),
5454
}
55-
mockOracle := &mockBoostrapOracle{bootInfo, false}
55+
mockOracle := newMockPreinteropBootstrapOracle(bootInfo, false)
5656
client := NewBootstrapClient(mockOracle)
5757
require.Panics(t, func() { client.BootInfo() })
5858
}
5959

60-
type mockBoostrapOracle struct {
60+
func newMockPreinteropBootstrapOracle(info *BootInfo, custom bool) *mockPreinteropBoostrapOracle {
61+
return &mockPreinteropBoostrapOracle{
62+
mockBoostrapOracle: mockBoostrapOracle{
63+
l1Head: info.L1Head,
64+
l2OutputRoot: info.L2OutputRoot,
65+
l2Claim: info.L2Claim,
66+
l2ClaimBlockNumber: info.L2ClaimBlockNumber,
67+
},
68+
b: info,
69+
custom: custom,
70+
}
71+
}
72+
73+
type mockPreinteropBoostrapOracle struct {
74+
mockBoostrapOracle
6175
b *BootInfo
6276
custom bool
6377
}
6478

65-
func (o *mockBoostrapOracle) Get(key preimage.Key) []byte {
79+
func (o *mockPreinteropBoostrapOracle) Get(key preimage.Key) []byte {
6680
switch key.PreimageKey() {
67-
case L1HeadLocalIndex.PreimageKey():
68-
return o.b.L1Head[:]
69-
case L2OutputRootLocalIndex.PreimageKey():
70-
return o.b.L2OutputRoot[:]
71-
case L2ClaimLocalIndex.PreimageKey():
72-
return o.b.L2Claim[:]
73-
case L2ClaimBlockNumberLocalIndex.PreimageKey():
74-
return binary.BigEndian.AppendUint64(nil, o.b.L2ClaimBlockNumber)
7581
case L2ChainIDLocalIndex.PreimageKey():
7682
return binary.BigEndian.AppendUint64(nil, o.b.L2ChainID)
7783
case L2ChainConfigLocalIndex.PreimageKey():
@@ -87,6 +93,6 @@ func (o *mockBoostrapOracle) Get(key preimage.Key) []byte {
8793
b, _ := json.Marshal(o.b.RollupConfig)
8894
return b
8995
default:
90-
panic("unknown key")
96+
return o.mockBoostrapOracle.Get(key)
9197
}
9298
}

0 commit comments

Comments
 (0)