Commit dd902048 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

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
parent e89248ad
......@@ -287,7 +287,7 @@ func testFaultProofProgramScenario(t *testing.T, ctx context.Context, sys *e2esy
preimageDir := t.TempDir()
fppConfig := oppconf.NewConfig(sys.RollupConfig, sys.L2GenesisCfg.Config, s.L1Head, s.L2Head, s.L2OutputRoot, common.Hash(s.L2Claim), s.L2ClaimBlockNumber)
fppConfig.L1URL = sys.NodeEndpoint("l1").RPC()
fppConfig.L2URL = sys.NodeEndpoint("sequencer").RPC()
fppConfig.L2URLs = []string{sys.NodeEndpoint("sequencer").RPC()}
fppConfig.L1BeaconURL = sys.L1BeaconEndpoint().RestHTTP()
fppConfig.DataDir = preimageDir
if s.Detached {
......@@ -314,7 +314,7 @@ func testFaultProofProgramScenario(t *testing.T, ctx context.Context, sys *e2esy
t.Log("Running fault proof in offline mode")
// Should be able to rerun in offline mode using the pre-fetched images
fppConfig.L1URL = ""
fppConfig.L2URL = ""
fppConfig.L2URLs = nil
err = opp.FaultProofProgramWithDefaultPrefecher(ctx, log, fppConfig)
require.NoError(t, err)
......
......@@ -6,24 +6,11 @@ import (
"math"
"github.com/ethereum-optimism/optimism/op-node/rollup"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-program/chainconfig"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
)
const (
L1HeadLocalIndex preimage.LocalIndexKey = iota + 1
L2OutputRootLocalIndex
L2ClaimLocalIndex
L2ClaimBlockNumberLocalIndex
L2ChainIDLocalIndex
// These local keys are only used for custom chains
L2ChainConfigLocalIndex
RollupConfigLocalIndex
)
// CustomChainIDIndicator is used to detect when the program should load custom chain configuration
const CustomChainIDIndicator = uint64(math.MaxUint64)
......@@ -38,10 +25,6 @@ type BootInfo struct {
RollupConfig *rollup.Config
}
type oracleClient interface {
Get(key preimage.Key) []byte
}
type BootstrapClient struct {
r oracleClient
}
......
package boot
import (
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-program/chainconfig"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
)
var (
ErrUnknownChainID = errors.New("unknown chain id")
)
type BootInfoInterop struct {
Configs ConfigSource
L1Head common.Hash
AgreedPrestate common.Hash
Claim common.Hash
ClaimTimestamp uint64
}
type ConfigSource interface {
RollupConfig(chainID uint64) (*rollup.Config, error)
ChainConfig(chainID uint64) (*params.ChainConfig, error)
}
type OracleConfigSource struct {
oracle oracleClient
customConfigsLoaded bool
l2ChainConfigs map[uint64]*params.ChainConfig
rollupConfigs map[uint64]*rollup.Config
}
func (c *OracleConfigSource) RollupConfig(chainID uint64) (*rollup.Config, error) {
if cfg, ok := c.rollupConfigs[chainID]; ok {
return cfg, nil
}
cfg, err := chainconfig.RollupConfigByChainID(chainID)
if !c.customConfigsLoaded && err != nil {
c.loadCustomConfigs()
if cfg, ok := c.rollupConfigs[chainID]; !ok {
return nil, fmt.Errorf("%w: %v", ErrUnknownChainID, chainID)
} else {
return cfg, nil
}
} else if err != nil {
return nil, err
}
c.rollupConfigs[chainID] = cfg
return cfg, nil
}
func (c *OracleConfigSource) ChainConfig(chainID uint64) (*params.ChainConfig, error) {
if cfg, ok := c.l2ChainConfigs[chainID]; ok {
return cfg, nil
}
cfg, err := chainconfig.ChainConfigByChainID(chainID)
if !c.customConfigsLoaded && err != nil {
c.loadCustomConfigs()
if cfg, ok := c.l2ChainConfigs[chainID]; !ok {
return nil, fmt.Errorf("%w: %v", ErrUnknownChainID, chainID)
} else {
return cfg, nil
}
} else if err != nil {
return nil, err
}
c.l2ChainConfigs[chainID] = cfg
return cfg, nil
}
func (c *OracleConfigSource) loadCustomConfigs() {
var rollupConfigs []*rollup.Config
err := json.Unmarshal(c.oracle.Get(RollupConfigLocalIndex), &rollupConfigs)
if err != nil {
panic("failed to bootstrap rollup configs")
}
for _, config := range rollupConfigs {
c.rollupConfigs[config.L2ChainID.Uint64()] = config
}
var chainConfigs []*params.ChainConfig
err = json.Unmarshal(c.oracle.Get(L2ChainConfigLocalIndex), &chainConfigs)
if err != nil {
panic("failed to bootstrap chain configs")
}
for _, config := range chainConfigs {
c.l2ChainConfigs[config.ChainID.Uint64()] = config
}
c.customConfigsLoaded = true
}
func BootstrapInterop(r oracleClient) *BootInfoInterop {
l1Head := common.BytesToHash(r.Get(L1HeadLocalIndex))
agreedPrestate := common.BytesToHash(r.Get(L2OutputRootLocalIndex))
claim := common.BytesToHash(r.Get(L2ClaimLocalIndex))
claimTimestamp := binary.BigEndian.Uint64(r.Get(L2ClaimBlockNumberLocalIndex))
return &BootInfoInterop{
Configs: &OracleConfigSource{
oracle: r,
l2ChainConfigs: make(map[uint64]*params.ChainConfig),
rollupConfigs: make(map[uint64]*rollup.Config),
},
L1Head: l1Head,
AgreedPrestate: agreedPrestate,
Claim: claim,
ClaimTimestamp: claimTimestamp,
}
}
package boot
import (
"encoding/json"
"fmt"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/rollup"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-program/chainconfig"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
func TestInteropBootstrap_SimpleValues(t *testing.T) {
expected := &BootInfoInterop{
L1Head: common.Hash{0xaa},
AgreedPrestate: common.Hash{0xbb},
Claim: common.Hash{0xcc},
ClaimTimestamp: 49829482,
}
mockOracle := newMockInteropBootstrapOracle(expected, false)
actual := BootstrapInterop(mockOracle)
require.Equal(t, expected.L1Head, actual.L1Head)
require.Equal(t, expected.AgreedPrestate, actual.AgreedPrestate)
require.Equal(t, expected.Claim, actual.Claim)
require.Equal(t, expected.ClaimTimestamp, actual.ClaimTimestamp)
}
func TestInteropBootstrap_RollupConfigBuiltIn(t *testing.T) {
expectedCfg := chaincfg.OPSepolia()
expected := &BootInfoInterop{
L1Head: common.Hash{0xaa},
AgreedPrestate: common.Hash{0xbb},
Claim: common.Hash{0xcc},
ClaimTimestamp: 49829482,
}
mockOracle := newMockInteropBootstrapOracle(expected, false)
actual := BootstrapInterop(mockOracle)
actualCfg, err := actual.Configs.RollupConfig(expectedCfg.L2ChainID.Uint64())
require.NoError(t, err)
require.Equal(t, expectedCfg, actualCfg)
}
func TestInteropBootstrap_RollupConfigCustom(t *testing.T) {
config1 := &rollup.Config{L2ChainID: big.NewInt(1111)}
config2 := &rollup.Config{L2ChainID: big.NewInt(2222)}
source := &BootInfoInterop{
L1Head: common.Hash{0xaa},
AgreedPrestate: common.Hash{0xbb},
Claim: common.Hash{0xcc},
ClaimTimestamp: 49829482,
}
mockOracle := newMockInteropBootstrapOracle(source, true)
mockOracle.rollupCfgs = []*rollup.Config{config1, config2}
actual := BootstrapInterop(mockOracle)
actualCfg, err := actual.Configs.RollupConfig(config1.L2ChainID.Uint64())
require.NoError(t, err)
require.Equal(t, config1, actualCfg)
actualCfg, err = actual.Configs.RollupConfig(config2.L2ChainID.Uint64())
require.NoError(t, err)
require.Equal(t, config2, actualCfg)
}
func TestInteropBootstrap_ChainConfigBuiltIn(t *testing.T) {
expectedCfg := chainconfig.OPSepoliaChainConfig()
expected := &BootInfoInterop{
L1Head: common.Hash{0xaa},
AgreedPrestate: common.Hash{0xbb},
Claim: common.Hash{0xcc},
ClaimTimestamp: 49829482,
}
mockOracle := newMockInteropBootstrapOracle(expected, false)
actual := BootstrapInterop(mockOracle)
actualCfg, err := actual.Configs.ChainConfig(expectedCfg.ChainID.Uint64())
require.NoError(t, err)
require.Equal(t, expectedCfg, actualCfg)
}
func TestInteropBootstrap_ChainConfigCustom(t *testing.T) {
config1 := &params.ChainConfig{ChainID: big.NewInt(1111)}
config2 := &params.ChainConfig{ChainID: big.NewInt(2222)}
expected := &BootInfoInterop{
L1Head: common.Hash{0xaa},
AgreedPrestate: common.Hash{0xbb},
Claim: common.Hash{0xcc},
ClaimTimestamp: 49829482,
}
mockOracle := newMockInteropBootstrapOracle(expected, true)
mockOracle.chainCfgs = []*params.ChainConfig{config1, config2}
actual := BootstrapInterop(mockOracle)
actualCfg, err := actual.Configs.ChainConfig(config1.ChainID.Uint64())
require.NoError(t, err)
require.Equal(t, config1, actualCfg)
actualCfg, err = actual.Configs.ChainConfig(config2.ChainID.Uint64())
require.NoError(t, err)
require.Equal(t, config2, actualCfg)
}
func newMockInteropBootstrapOracle(b *BootInfoInterop, custom bool) *mockInteropBootstrapOracle {
return &mockInteropBootstrapOracle{
mockBoostrapOracle: mockBoostrapOracle{
l1Head: b.L1Head,
l2OutputRoot: b.AgreedPrestate,
l2Claim: b.Claim,
l2ClaimBlockNumber: b.ClaimTimestamp,
},
custom: custom,
}
}
type mockInteropBootstrapOracle struct {
mockBoostrapOracle
rollupCfgs []*rollup.Config
chainCfgs []*params.ChainConfig
custom bool
}
func (o *mockInteropBootstrapOracle) Get(key preimage.Key) []byte {
switch key.PreimageKey() {
case L2ChainConfigLocalIndex.PreimageKey():
if !o.custom {
panic(fmt.Sprintf("unexpected oracle request for preimage key %x", key.PreimageKey()))
}
b, _ := json.Marshal(o.chainCfgs)
return b
case RollupConfigLocalIndex.PreimageKey():
if !o.custom {
panic(fmt.Sprintf("unexpected oracle request for preimage key %x", key.PreimageKey()))
}
b, _ := json.Marshal(o.rollupCfgs)
return b
default:
return o.mockBoostrapOracle.Get(key)
}
}
......@@ -24,7 +24,7 @@ func TestBootstrapClient(t *testing.T) {
L2ChainConfig: chainconfig.OPSepoliaChainConfig(),
RollupConfig: rollupCfg,
}
mockOracle := &mockBoostrapOracle{bootInfo, false}
mockOracle := newMockPreinteropBootstrapOracle(bootInfo, false)
readBootInfo := NewBootstrapClient(mockOracle).BootInfo()
require.EqualValues(t, bootInfo, readBootInfo)
}
......@@ -39,7 +39,7 @@ func TestBootstrapClient_CustomChain(t *testing.T) {
L2ChainConfig: chainconfig.OPSepoliaChainConfig(),
RollupConfig: chaincfg.OPSepolia(),
}
mockOracle := &mockBoostrapOracle{bootInfo, true}
mockOracle := newMockPreinteropBootstrapOracle(bootInfo, true)
readBootInfo := NewBootstrapClient(mockOracle).BootInfo()
require.EqualValues(t, bootInfo, readBootInfo)
}
......@@ -52,26 +52,32 @@ func TestBootstrapClient_UnknownChainPanics(t *testing.T) {
L2ClaimBlockNumber: 1,
L2ChainID: uint64(0xdead),
}
mockOracle := &mockBoostrapOracle{bootInfo, false}
mockOracle := newMockPreinteropBootstrapOracle(bootInfo, false)
client := NewBootstrapClient(mockOracle)
require.Panics(t, func() { client.BootInfo() })
}
type mockBoostrapOracle struct {
func newMockPreinteropBootstrapOracle(info *BootInfo, custom bool) *mockPreinteropBoostrapOracle {
return &mockPreinteropBoostrapOracle{
mockBoostrapOracle: mockBoostrapOracle{
l1Head: info.L1Head,
l2OutputRoot: info.L2OutputRoot,
l2Claim: info.L2Claim,
l2ClaimBlockNumber: info.L2ClaimBlockNumber,
},
b: info,
custom: custom,
}
}
type mockPreinteropBoostrapOracle struct {
mockBoostrapOracle
b *BootInfo
custom bool
}
func (o *mockBoostrapOracle) Get(key preimage.Key) []byte {
func (o *mockPreinteropBoostrapOracle) Get(key preimage.Key) []byte {
switch key.PreimageKey() {
case L1HeadLocalIndex.PreimageKey():
return o.b.L1Head[:]
case L2OutputRootLocalIndex.PreimageKey():
return o.b.L2OutputRoot[:]
case L2ClaimLocalIndex.PreimageKey():
return o.b.L2Claim[:]
case L2ClaimBlockNumberLocalIndex.PreimageKey():
return binary.BigEndian.AppendUint64(nil, o.b.L2ClaimBlockNumber)
case L2ChainIDLocalIndex.PreimageKey():
return binary.BigEndian.AppendUint64(nil, o.b.L2ChainID)
case L2ChainConfigLocalIndex.PreimageKey():
......@@ -87,6 +93,6 @@ func (o *mockBoostrapOracle) Get(key preimage.Key) []byte {
b, _ := json.Marshal(o.b.RollupConfig)
return b
default:
panic("unknown key")
return o.mockBoostrapOracle.Get(key)
}
}
package boot
import preimage "github.com/ethereum-optimism/optimism/op-preimage"
const (
L1HeadLocalIndex preimage.LocalIndexKey = iota + 1
L2OutputRootLocalIndex
L2ClaimLocalIndex
L2ClaimBlockNumberLocalIndex
L2ChainIDLocalIndex
// These local keys are only used for custom chains
L2ChainConfigLocalIndex
RollupConfigLocalIndex
)
type oracleClient interface {
Get(key preimage.Key) []byte
}
package boot
import (
"encoding/binary"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum/go-ethereum/common"
)
type mockBoostrapOracle struct {
l1Head common.Hash
l2OutputRoot common.Hash
l2Claim common.Hash
l2ClaimBlockNumber uint64
}
func (o *mockBoostrapOracle) Get(key preimage.Key) []byte {
switch key.PreimageKey() {
case L1HeadLocalIndex.PreimageKey():
return o.l1Head[:]
case L2OutputRootLocalIndex.PreimageKey():
return o.l2OutputRoot[:]
case L2ClaimLocalIndex.PreimageKey():
return o.l2Claim[:]
case L2ClaimBlockNumberLocalIndex.PreimageKey():
return binary.BigEndian.AppendUint64(nil, o.l2ClaimBlockNumber)
default:
panic("unknown key")
}
}
......@@ -33,16 +33,16 @@ type taskExecutor interface {
l2Oracle l2.Oracle) (tasks.DerivationResult, error)
}
func RunInteropProgram(logger log.Logger, bootInfo *boot.BootInfo, l1PreimageOracle l1.Oracle, l2PreimageOracle l2.Oracle, validateClaim bool) error {
func RunInteropProgram(logger log.Logger, bootInfo *boot.BootInfoInterop, l1PreimageOracle l1.Oracle, l2PreimageOracle l2.Oracle, validateClaim bool) error {
return runInteropProgram(logger, bootInfo, l1PreimageOracle, l2PreimageOracle, validateClaim, &interopTaskExecutor{})
}
func runInteropProgram(logger log.Logger, bootInfo *boot.BootInfo, l1PreimageOracle l1.Oracle, l2PreimageOracle l2.Oracle, validateClaim bool, tasks taskExecutor) error {
func runInteropProgram(logger log.Logger, bootInfo *boot.BootInfoInterop, l1PreimageOracle l1.Oracle, l2PreimageOracle l2.Oracle, validateClaim bool, tasks taskExecutor) error {
logger.Info("Interop Program Bootstrapped", "bootInfo", bootInfo)
// For the first step in a timestamp, we would get a SuperRoot as the agreed claim - TransitionStateByRoot will
// automatically convert it to a TransitionState with Step: 0.
transitionState := l2PreimageOracle.TransitionStateByRoot(bootInfo.L2OutputRoot)
transitionState := l2PreimageOracle.TransitionStateByRoot(bootInfo.AgreedPrestate)
if transitionState.Version() != types.IntermediateTransitionVersion {
return fmt.Errorf("%w: %v", ErrIncorrectOutputRootType, transitionState.Version())
}
......@@ -55,16 +55,25 @@ func runInteropProgram(logger log.Logger, bootInfo *boot.BootInfo, l1PreimageOra
return fmt.Errorf("%w: %v", ErrIncorrectOutputRootType, super.Version())
}
superRoot := super.(*eth.SuperV1)
claimedBlockNumber, err := bootInfo.RollupConfig.TargetBlockNumber(superRoot.Timestamp + 1)
chainAgreedPrestate := superRoot.Chains[transitionState.Step]
rollupCfg, err := bootInfo.Configs.RollupConfig(chainAgreedPrestate.ChainID)
if err != nil {
return fmt.Errorf("no rollup config available for chain ID %v: %w", chainAgreedPrestate.ChainID, err)
}
l2ChainConfig, err := bootInfo.Configs.ChainConfig(chainAgreedPrestate.ChainID)
if err != nil {
return fmt.Errorf("no chain config available for chain ID %v: %w", chainAgreedPrestate.ChainID, err)
}
claimedBlockNumber, err := rollupCfg.TargetBlockNumber(superRoot.Timestamp + 1)
if err != nil {
return err
}
derivationResult, err := tasks.RunDerivation(
logger,
bootInfo.RollupConfig,
bootInfo.L2ChainConfig,
rollupCfg,
l2ChainConfig,
bootInfo.L1Head,
superRoot.Chains[transitionState.Step].Output,
chainAgreedPrestate.Output,
claimedBlockNumber,
l1PreimageOracle,
l2PreimageOracle,
......@@ -91,7 +100,7 @@ func runInteropProgram(logger log.Logger, bootInfo *boot.BootInfo, l1PreimageOra
if !validateClaim {
return nil
}
return claim.ValidateClaim(logger, derivationResult.Head, eth.Bytes32(bootInfo.L2Claim), eth.Bytes32(expected))
return claim.ValidateClaim(logger, derivationResult.Head, eth.Bytes32(bootInfo.Claim), eth.Bytes32(expected))
}
type interopTaskExecutor struct {
......
package interop
import (
"fmt"
"testing"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-program/chainconfig"
"github.com/ethereum-optimism/optimism/op-program/client/boot"
"github.com/ethereum-optimism/optimism/op-program/client/interop/types"
"github.com/ethereum-optimism/optimism/op-program/client/l1"
......@@ -22,6 +24,7 @@ import (
func TestDeriveBlockForFirstChainFromSuperchainRoot(t *testing.T) {
logger := testlog.Logger(t, log.LevelError)
rollupCfg := chaincfg.OPSepolia()
chainCfg := chainconfig.OPSepoliaChainConfig()
chain1Output := &eth.OutputV0{}
agreedSuperRoot := &eth.SuperV1{
Timestamp: rollupCfg.Genesis.L2Time + 1234,
......@@ -48,11 +51,14 @@ func TestDeriveBlockForFirstChainFromSuperchainRoot(t *testing.T) {
expectedClaim, err := expectedIntermediateRoot.Hash()
require.NoError(t, err)
bootInfo := &boot.BootInfo{
L2OutputRoot: outputRootHash,
RollupConfig: rollupCfg,
L2ClaimBlockNumber: agreedSuperRoot.Timestamp + 1,
L2Claim: expectedClaim,
bootInfo := &boot.BootInfoInterop{
AgreedPrestate: outputRootHash,
ClaimTimestamp: agreedSuperRoot.Timestamp + 1,
Claim: expectedClaim,
Configs: &staticConfigSource{
rollupCfgs: []*rollup.Config{rollupCfg},
chainConfigs: []*params.ChainConfig{chainCfg},
},
}
err = runInteropProgram(logger, bootInfo, nil, l2PreimageOracle, true, &tasks)
require.NoError(t, err)
......@@ -80,3 +86,26 @@ func (t *stubTasks) RunDerivation(
OutputRoot: t.outputRoot,
}, t.err
}
type staticConfigSource struct {
rollupCfgs []*rollup.Config
chainConfigs []*params.ChainConfig
}
func (s *staticConfigSource) RollupConfig(chainID uint64) (*rollup.Config, error) {
for _, cfg := range s.rollupCfgs {
if cfg.L2ChainID.Uint64() == chainID {
return cfg, nil
}
}
return nil, fmt.Errorf("no rollup config found for chain %d", chainID)
}
func (s *staticConfigSource) ChainConfig(chainID uint64) (*params.ChainConfig, error) {
for _, cfg := range s.chainConfigs {
if cfg.ChainID.Uint64() == chainID {
return cfg, nil
}
}
return nil, fmt.Errorf("no chain config found for chain %d", chainID)
}
......@@ -47,9 +47,10 @@ func RunProgram(logger log.Logger, preimageOracle io.ReadWriter, preimageHinter
l1PreimageOracle := l1.NewCachingOracle(l1.NewPreimageOracle(pClient, hClient))
l2PreimageOracle := l2.NewCachingOracle(l2.NewPreimageOracle(pClient, hClient))
bootInfo := boot.NewBootstrapClient(pClient).BootInfo()
if cfg.InteropEnabled {
bootInfo := boot.BootstrapInterop(pClient)
return interop.RunInteropProgram(logger, bootInfo, l1PreimageOracle, l2PreimageOracle, !cfg.SkipValidation)
}
bootInfo := boot.NewBootstrapClient(pClient).BootInfo()
return RunPreInteropProgram(logger, bootInfo, l1PreimageOracle, l2PreimageOracle)
}
......@@ -15,6 +15,7 @@ import (
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
......@@ -102,7 +103,8 @@ func TestNetwork(t *testing.T) {
genesisFile := writeValidGenesis(t)
cfg := configForArgs(t, addRequiredArgsExcept("--network", "--rollup.config", configFile, "--l2.genesis", genesisFile))
require.Equal(t, *chaincfg.OPSepolia(), *cfg.Rollup)
require.Len(t, cfg.Rollups, 1)
require.Equal(t, *chaincfg.OPSepolia(), *cfg.Rollups[0])
})
for _, name := range chaincfg.AvailableNetworks() {
......@@ -112,7 +114,8 @@ func TestNetwork(t *testing.T) {
t.Run("Network_"+name, func(t *testing.T) {
args := replaceRequiredArg("--network", name)
cfg := configForArgs(t, args)
require.Equal(t, *expected, *cfg.Rollup)
require.Len(t, cfg.Rollups, 1)
require.Equal(t, *expected, *cfg.Rollups[0])
})
}
}
......@@ -140,7 +143,7 @@ func TestDataFormat(t *testing.T) {
func TestL2(t *testing.T) {
expected := "https://example.com:8545"
cfg := configForArgs(t, addRequiredArgs("--l2", expected))
require.Equal(t, expected, cfg.L2URL)
require.Equal(t, []string{expected}, cfg.L2URLs)
}
func TestL2Genesis(t *testing.T) {
......@@ -153,12 +156,12 @@ func TestL2Genesis(t *testing.T) {
rollupCfgFile := writeValidRollupConfig(t)
genesisFile := writeValidGenesis(t)
cfg := configForArgs(t, addRequiredArgsExcept("--network", "--rollup.config", rollupCfgFile, "--l2.genesis", genesisFile))
require.Equal(t, l2GenesisConfig, cfg.L2ChainConfig)
require.Equal(t, []*params.ChainConfig{l2GenesisConfig}, cfg.L2ChainConfigs)
})
t.Run("NotRequiredForSepolia", func(t *testing.T) {
cfg := configForArgs(t, replaceRequiredArg("--network", "sepolia"))
require.Equal(t, chainconfig.OPSepoliaChainConfig(), cfg.L2ChainConfig)
require.Equal(t, []*params.ChainConfig{chainconfig.OPSepoliaChainConfig()}, cfg.L2ChainConfigs)
})
}
......@@ -334,13 +337,13 @@ func TestL2Claim(t *testing.T) {
func TestL2Experimental(t *testing.T) {
t.Run("DefaultEmpty", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs())
require.Equal(t, cfg.L2ExperimentalURL, "")
require.Len(t, cfg.L2ExperimentalURLs, 0)
})
t.Run("Valid", func(t *testing.T) {
expected := "https://example.com:8545"
cfg := configForArgs(t, replaceRequiredArg("--l2.experimental", expected))
require.EqualValues(t, expected, cfg.L2ExperimentalURL)
cfg := configForArgs(t, addRequiredArgs("--l2.experimental", expected))
require.EqualValues(t, []string{expected}, cfg.L2ExperimentalURLs)
})
}
......
......@@ -3,10 +3,8 @@ package common
import (
"context"
"errors"
"time"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-program/host/config"
hosttypes "github.com/ethereum-optimism/optimism/op-program/host/types"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/eth"
......@@ -49,27 +47,6 @@ func NewL2SourceWithClient(logger log.Logger, canonicalL2Client *L2Client, canon
return source
}
func NewL2Source(ctx context.Context, logger log.Logger, config *config.Config) (*L2Source, error) {
logger.Info("Connecting to canonical L2 source", "url", config.L2URL)
// eth_getProof calls are expensive and takes time, so we use a longer timeout
canonicalL2RPC, err := client.NewRPC(ctx, logger, config.L2URL, client.WithDialAttempts(10), client.WithCallTimeout(5*time.Minute))
if err != nil {
return nil, err
}
var experimentalRPC client.RPC
if len(config.L2ExperimentalURL) != 0 {
logger.Info("Connecting to experimental L2 source", "url", config.L2ExperimentalURL)
// debug_executionWitness calls are expensive and takes time, so we use a longer timeout
experimentalRPC, err = client.NewRPC(ctx, logger, config.L2ExperimentalURL, client.WithDialAttempts(10), client.WithCallTimeout(5*time.Minute))
if err != nil {
return nil, err
}
}
return NewL2SourceFromRPC(logger, config.Rollup, canonicalL2RPC, experimentalRPC)
}
func NewL2SourceFromRPC(logger log.Logger, rollupCfg *rollup.Config, canonicalL2RPC client.RPC, experimentalRPC client.RPC) (*L2Source, error) {
canonicalDebugClient := sources.NewDebugClient(canonicalL2RPC.CallContext)
......
package common
import (
"context"
"errors"
"fmt"
"math/big"
"time"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
)
var (
ErrNoSources = errors.New("no sources specified")
ErrNoL2ForRollup = errors.New("no L2 RPC available for rollup")
ErrNoRollupForL2 = errors.New("no rollup config available for L2 RPC")
ErrNoRollupForExperimental = errors.New("no rollup config available for L2 experimental RPC")
)
type L2Sources struct {
Sources map[uint64]*L2Source
}
func NewL2SourcesFromURLs(ctx context.Context, logger log.Logger, configs []*rollup.Config, l2URLs []string, l2ExperimentalURLs []string) (*L2Sources, error) {
l2Clients, err := connectRPCs(ctx, logger, l2URLs)
if err != nil {
return nil, err
}
l2ExperimentalClients, err := connectRPCs(ctx, logger, l2URLs)
if err != nil {
return nil, err
}
return NewL2Sources(ctx, logger, configs, l2Clients, l2ExperimentalClients)
}
func connectRPCs(ctx context.Context, logger log.Logger, urls []string) ([]client.RPC, error) {
l2Clients := make([]client.RPC, len(urls))
for i, url := range urls {
logger.Info("Connecting to L2 source", "url", url)
// eth_getProof calls are expensive and takes time, so we use a longer timeout
rpc, err := client.NewRPC(ctx, logger, url, client.WithDialAttempts(10), client.WithCallTimeout(5*time.Minute))
if err != nil {
return nil, fmt.Errorf("failed to connect to rpc URL %s: %w", url, err)
}
l2Clients[i] = rpc
}
return l2Clients, nil
}
func NewL2Sources(ctx context.Context, logger log.Logger, configs []*rollup.Config, l2Clients []client.RPC, l2ExperimentalClients []client.RPC) (*L2Sources, error) {
if len(configs) == 0 {
return nil, ErrNoSources
}
rollupConfigs := make(map[uint64]*rollup.Config)
for _, rollupCfg := range configs {
rollupConfigs[rollupCfg.L2ChainID.Uint64()] = rollupCfg
}
l2RPCs := make(map[uint64]client.RPC)
for _, rpc := range l2Clients {
chainID, err := loadChainID(ctx, rpc)
if err != nil {
return nil, fmt.Errorf("failed to load chain ID: %w", err)
}
l2RPCs[chainID] = rpc
if _, ok := rollupConfigs[chainID]; !ok {
return nil, fmt.Errorf("%w: %v", ErrNoRollupForL2, chainID)
}
}
l2ExperimentalRPCs := make(map[uint64]client.RPC)
for _, rpc := range l2ExperimentalClients {
chainID, err := loadChainID(ctx, rpc)
if err != nil {
return nil, fmt.Errorf("failed to load chain ID: %w", err)
}
l2ExperimentalRPCs[chainID] = rpc
if _, ok := rollupConfigs[chainID]; !ok {
return nil, fmt.Errorf("%w: %v", ErrNoRollupForExperimental, chainID)
}
}
sources := make(map[uint64]*L2Source)
for _, rollupCfg := range rollupConfigs {
chainID := rollupCfg.L2ChainID.Uint64()
l2RPC, ok := l2RPCs[chainID]
if !ok {
return nil, fmt.Errorf("%w: %v", ErrNoL2ForRollup, chainID)
}
l2ExperimentalRPC := l2ExperimentalRPCs[chainID] // Allowed to be nil
source, err := NewL2SourceFromRPC(logger, rollupCfg, l2RPC, l2ExperimentalRPC)
if err != nil {
return nil, fmt.Errorf("failed to create l2 source for chain ID %v: %w", chainID, err)
}
sources[chainID] = source
}
return &L2Sources{
Sources: sources,
}, nil
}
func loadChainID(ctx context.Context, rpc client.RPC) (uint64, error) {
var id hexutil.Big
err := rpc.CallContext(ctx, &id, "eth_chainId")
if err != nil {
return 0, err
}
return (*big.Int)(&id).Uint64(), nil
}
package common
import (
"context"
"fmt"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
)
func TestNewL2Sources(t *testing.T) {
t.Run("NoSources", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelInfo)
_, err := NewL2Sources(context.Background(), logger, nil, nil, nil)
require.ErrorIs(t, err, ErrNoSources)
})
t.Run("SingleSource", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelDebug)
config, l2Rpc, experimentalRpc := chain(4)
src, err := NewL2Sources(context.Background(), logger,
[]*rollup.Config{config},
[]client.RPC{l2Rpc},
[]client.RPC{experimentalRpc})
require.NoError(t, err)
require.Len(t, src.Sources, 1)
require.True(t, src.Sources[uint64(4)].ExperimentalEnabled())
})
t.Run("MultipleSources", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelDebug)
config1, l2Rpc1, experimentalRpc1 := chain(1)
config2, l2Rpc2, experimentalRpc2 := chain(2)
src, err := NewL2Sources(context.Background(), logger,
[]*rollup.Config{config1, config2},
[]client.RPC{l2Rpc1, l2Rpc2},
[]client.RPC{experimentalRpc1, experimentalRpc2})
require.NoError(t, err)
require.Len(t, src.Sources, 2)
require.True(t, src.Sources[uint64(1)].ExperimentalEnabled())
require.True(t, src.Sources[uint64(2)].ExperimentalEnabled())
})
t.Run("ExperimentalRPCsAreOptional", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelDebug)
config1, l2Rpc1, _ := chain(1)
config2, l2Rpc2, experimentalRpc2 := chain(2)
src, err := NewL2Sources(context.Background(), logger,
[]*rollup.Config{config1, config2},
[]client.RPC{l2Rpc1, l2Rpc2},
[]client.RPC{experimentalRpc2})
require.NoError(t, err)
require.Len(t, src.Sources, 2)
require.Same(t, src.Sources[uint64(1)].RollupConfig(), config1)
require.False(t, src.Sources[uint64(1)].ExperimentalEnabled())
require.Same(t, src.Sources[uint64(2)].RollupConfig(), config2)
require.True(t, src.Sources[uint64(2)].ExperimentalEnabled())
})
t.Run("RollupMissingL2URL", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelDebug)
config1, _, _ := chain(1)
config2, l2Rpc2, experimentalRpc2 := chain(2)
_, err := NewL2Sources(context.Background(), logger,
[]*rollup.Config{config1, config2},
[]client.RPC{l2Rpc2},
[]client.RPC{experimentalRpc2})
require.ErrorIs(t, err, ErrNoL2ForRollup)
})
t.Run("L2URLWithoutConfig", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelDebug)
_, l2Rpc1, _ := chain(1)
config2, l2Rpc2, experimentalRpc2 := chain(2)
_, err := NewL2Sources(context.Background(), logger,
[]*rollup.Config{config2},
[]client.RPC{l2Rpc1, l2Rpc2},
[]client.RPC{experimentalRpc2})
require.ErrorIs(t, err, ErrNoRollupForL2)
})
t.Run("ExperimentalURLWithoutConfig", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelDebug)
_, _, experimentalRpc1 := chain(1)
config2, l2Rpc2, experimentalRpc2 := chain(2)
_, err := NewL2Sources(context.Background(), logger,
[]*rollup.Config{config2},
[]client.RPC{l2Rpc2},
[]client.RPC{experimentalRpc1, experimentalRpc2})
require.ErrorIs(t, err, ErrNoRollupForExperimental)
})
}
func chain(id uint64) (*rollup.Config, client.RPC, client.RPC) {
chainID := new(big.Int).SetUint64(id)
return &rollup.Config{L2ChainID: chainID}, &chainIDRPC{id: chainID}, &chainIDRPC{id: chainID}
}
type chainIDRPC struct {
id *big.Int
}
func (c *chainIDRPC) Close() {
panic("implement me")
}
func (c *chainIDRPC) CallContext(ctx context.Context, result any, method string, args ...any) error {
if method != "eth_chainId" {
return fmt.Errorf("invalid method: %s", method)
}
resultOut := result.(*hexutil.Big)
*resultOut = (hexutil.Big)(*c.id)
return nil
}
func (c *chainIDRPC) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error {
panic("implement me")
}
func (c *chainIDRPC) Subscribe(ctx context.Context, namespace string, channel any, args ...any) (ethereum.Subscription, error) {
panic("implement me")
}
......@@ -41,8 +41,8 @@ var (
)
type Config struct {
L2ChainID uint64
Rollup *rollup.Config
L2ChainID uint64 // TODO: Forbid for interop
Rollups []*rollup.Config
// DataDir is the directory to read/write pre-image data from/to.
// If not set, an in-memory key-value store is used and fetching data must be enabled
DataDir string
......@@ -57,22 +57,26 @@ type Config struct {
L1TrustRPC bool
L1RPCKind sources.RPCProviderKind
// L2Head is the l2 block hash contained in the L2 Output referenced by the L2OutputRoot
L2Head common.Hash
// L2Head is the l2 block hash contained in the L2 Output referenced by the L2OutputRoot for pre-interop mode
L2Head common.Hash // TODO: Forbid for interop
// L2OutputRoot is the agreed L2 output root to start derivation from
L2OutputRoot common.Hash
// L2URL is the URL of the L2 node to fetch L2 data from, this is the canonical URL for L2 data
// This URL is used as a fallback for L2ExperimentalURL if the experimental URL fails or cannot retrieve the desired data
L2URL string
// L2ExperimentalURL is the URL of the L2 node (non hash db archival node, for example, reth archival node) to fetch L2 data from
L2ExperimentalURL string
// L2URLs are the URLs of the L2 nodes to fetch L2 data from, these are the canonical URL for L2 data
// These URLs are used as a fallback for L2ExperimentalURL if the experimental URL fails or cannot retrieve the desired data
// Must have one L2URL for each chain in Rollups
L2URLs []string
// L2ExperimentalURLs are the URLs of the L2 nodes (non hash db archival node, for example, reth archival node) to fetch L2 data from
// Must have one url for each chain in Rollups
L2ExperimentalURLs []string
// L2Claim is the claimed L2 output root to verify
L2Claim common.Hash
// L2ClaimBlockNumber is the block number the claimed L2 output root is from
// Must be above 0 and to be a valid claim needs to be above the L2Head block.
// For interop this is the superchain root timestamp
L2ClaimBlockNumber uint64
// L2ChainConfig is the op-geth chain config for the L2 execution engine
L2ChainConfig *params.ChainConfig
// L2ChainConfigs are the op-geth chain config for the L2 execution engines
// Must have one chain config for each rollup config
L2ChainConfigs []*params.ChainConfig
// ExecCmd specifies the client program to execute in a separate process.
// If unset, the fault proof client is run in the same process.
ExecCmd string
......@@ -88,11 +92,13 @@ type Config struct {
}
func (c *Config) Check() error {
if c.Rollup == nil {
if len(c.Rollups) == 0 {
return ErrMissingRollupConfig
}
if err := c.Rollup.Check(); err != nil {
return err
for _, rollupCfg := range c.Rollups {
if err := rollupCfg.Check(); err != nil {
return fmt.Errorf("invalid rollup config for chain %v: %w", rollupCfg.L2ChainID, err)
}
}
if c.L1Head == (common.Hash{}) {
return ErrInvalidL1Head
......@@ -106,10 +112,10 @@ func (c *Config) Check() error {
if c.L2ClaimBlockNumber == 0 {
return ErrInvalidL2ClaimBlock
}
if c.L2ChainConfig == nil {
if len(c.L2ChainConfigs) == 0 {
return ErrMissingL2Genesis
}
if (c.L1URL != "") != (c.L2URL != "") {
if (c.L1URL != "") != (len(c.L2URLs) > 0) {
return ErrL1AndL2Inconsistent
}
if !c.FetchingEnabled() && c.DataDir == "" {
......@@ -133,7 +139,7 @@ func (c *Config) Check() error {
}
func (c *Config) FetchingEnabled() bool {
return c.L1URL != "" && c.L2URL != "" && c.L1BeaconURL != ""
return c.L1URL != "" && len(c.L2URLs) > 0 && c.L1BeaconURL != ""
}
// NewConfig creates a Config with all optional values set to the CLI default value
......@@ -154,8 +160,8 @@ func NewConfig(
}
return &Config{
L2ChainID: l2ChainID,
Rollup: rollupCfg,
L2ChainConfig: l2Genesis,
Rollups: []*rollup.Config{rollupCfg},
L2ChainConfigs: []*params.ChainConfig{l2Genesis},
L1Head: l1Head,
L2Head: l2Head,
L2OutputRoot: l2OutputRoot,
......@@ -252,14 +258,22 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) {
if !slices.Contains(types.SupportedDataFormats, dbFormat) {
return nil, fmt.Errorf("invalid %w: %v", ErrInvalidDataFormat, dbFormat)
}
var l2URLs []string
if ctx.IsSet(flags.L2NodeAddr.Name) {
l2URLs = append(l2URLs, ctx.String(flags.L2NodeAddr.Name))
}
var l2ExperimentalURLs []string
if ctx.IsSet(flags.L2NodeExperimentalAddr.Name) {
l2ExperimentalURLs = append(l2ExperimentalURLs, ctx.String(flags.L2NodeExperimentalAddr.Name))
}
return &Config{
L2ChainID: l2ChainID,
Rollup: rollupCfg,
Rollups: []*rollup.Config{rollupCfg},
DataDir: ctx.String(flags.DataDir.Name),
DataFormat: dbFormat,
L2URL: ctx.String(flags.L2NodeAddr.Name),
L2ExperimentalURL: ctx.String(flags.L2NodeExperimentalAddr.Name),
L2ChainConfig: l2ChainConfig,
L2URLs: l2URLs,
L2ExperimentalURLs: l2ExperimentalURLs,
L2ChainConfigs: []*params.ChainConfig{l2ChainConfig},
L2Head: l2Head,
L2OutputRoot: l2OutputRoot,
AgreedPrestate: agreedPrestate,
......
......@@ -35,14 +35,14 @@ func TestValidConfigIsValid(t *testing.T) {
func TestRollupConfig(t *testing.T) {
t.Run("Required", func(t *testing.T) {
config := validConfig()
config.Rollup = nil
config.Rollups = nil
err := config.Check()
require.ErrorIs(t, err, ErrMissingRollupConfig)
})
t.Run("Invalid", func(t *testing.T) {
config := validConfig()
config.Rollup = &rollup.Config{}
config.Rollups = []*rollup.Config{&rollup.Config{}}
err := config.Check()
require.ErrorIs(t, err, rollup.ErrBlockTimeZero)
})
......@@ -85,7 +85,7 @@ func TestL2ClaimBlockNumberRequired(t *testing.T) {
func TestL2GenesisRequired(t *testing.T) {
config := validConfig()
config.L2ChainConfig = nil
config.L2ChainConfigs = nil
err := config.Check()
require.ErrorIs(t, err, ErrMissingL2Genesis)
}
......@@ -98,19 +98,25 @@ func TestFetchingArgConsistency(t *testing.T) {
})
t.Run("RequireL1WhenL2Set", func(t *testing.T) {
cfg := validConfig()
cfg.L2URL = "https://example.com:1234"
cfg.L2URLs = []string{"https://example.com:1234"}
require.ErrorIs(t, cfg.Check(), ErrL1AndL2Inconsistent)
})
t.Run("AllowNeitherSet", func(t *testing.T) {
cfg := validConfig()
cfg.L1URL = ""
cfg.L2URL = ""
cfg.L2URLs = []string{}
require.NoError(t, cfg.Check())
})
t.Run("AllowNeitherSetNil", func(t *testing.T) {
cfg := validConfig()
cfg.L1URL = ""
cfg.L2URLs = nil
require.NoError(t, cfg.Check())
})
t.Run("AllowBothSet", func(t *testing.T) {
cfg := validConfig()
cfg.L1URL = "https://example.com:1234"
cfg.L2URL = "https://example.com:4678"
cfg.L2URLs = []string{"https://example.com:4678"}
require.NoError(t, cfg.Check())
})
}
......@@ -123,13 +129,13 @@ func TestFetchingEnabled(t *testing.T) {
t.Run("FetchingEnabledWhenFetcherUrlsSpecified", func(t *testing.T) {
cfg := validConfig()
cfg.L2URL = "https://example.com:1234"
cfg.L2URLs = []string{"https://example.com:1234"}
require.False(t, cfg.FetchingEnabled(), "Should not enable fetching when node URL not supplied")
})
t.Run("FetchingNotEnabledWhenNoL1UrlSpecified", func(t *testing.T) {
cfg := validConfig()
cfg.L2URL = "https://example.com:1234"
cfg.L2URLs = []string{"https://example.com:1234"}
require.False(t, cfg.FetchingEnabled(), "Should not enable L1 fetching when L1 node URL not supplied")
})
......@@ -143,7 +149,7 @@ func TestFetchingEnabled(t *testing.T) {
cfg := validConfig()
cfg.L1URL = "https://example.com:1234"
cfg.L1BeaconURL = "https://example.com:5678"
cfg.L2URL = "https://example.com:91011"
cfg.L2URLs = []string{"https://example.com:91011"}
require.True(t, cfg.FetchingEnabled(), "Should enable fetching when node URL supplied")
})
}
......@@ -152,7 +158,7 @@ func TestRequireDataDirInNonFetchingMode(t *testing.T) {
cfg := validConfig()
cfg.DataDir = ""
cfg.L1URL = ""
cfg.L2URL = ""
cfg.L2URLs = nil
err := cfg.Check()
require.ErrorIs(t, err, ErrDataDirRequired)
}
......
......@@ -5,7 +5,6 @@ import (
"fmt"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/rollup"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
hostcommon "github.com/ethereum-optimism/optimism/op-program/host/common"
"github.com/ethereum-optimism/optimism/op-program/host/config"
......@@ -43,7 +42,9 @@ func Main(logger log.Logger, cfg *config.Config) error {
return fmt.Errorf("invalid config: %w", err)
}
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, logger)
cfg.Rollup.LogDescription(logger, chaincfg.L2ChainIDToNetworkDisplayName)
for _, r := range cfg.Rollups {
r.LogDescription(logger, chaincfg.L2ChainIDToNetworkDisplayName)
}
hostCtx, stop := ctxinterrupt.WithSignalWaiter(context.Background())
defer stop()
......@@ -79,7 +80,8 @@ func makeDefaultPrefetcher(ctx context.Context, logger log.Logger, kv kvstore.KV
return nil, fmt.Errorf("failed to setup L1 RPC: %w", err)
}
l1ClCfg := sources.L1ClientDefaultConfig(cfg.Rollup, cfg.L1TrustRPC, cfg.L1RPCKind)
// Small cache because we store everything to the KV store, but 0 isn't allowed.
l1ClCfg := sources.L1ClientSimpleConfig(cfg.L1TrustRPC, cfg.L1RPCKind, 100)
l1Cl, err := sources.NewL1Client(l1RPC, logger, nil, l1ClCfg)
if err != nil {
return nil, fmt.Errorf("failed to create L1 client: %w", err)
......@@ -90,17 +92,13 @@ func makeDefaultPrefetcher(ctx context.Context, logger log.Logger, kv kvstore.KV
l1BlobFetcher := sources.NewL1BeaconClient(l1Beacon, sources.L1BeaconClientConfig{FetchAllSidecars: false})
logger.Info("Initializing L2 clients")
var experimentalURLs []string
if cfg.L2ExperimentalURL != "" {
experimentalURLs = append(experimentalURLs, cfg.L2ExperimentalURL)
}
sources, err := prefetcher.NewRetryingL2SourcesFromURLs(ctx, logger, []*rollup.Config{cfg.Rollup}, []string{cfg.L2URL}, experimentalURLs)
sources, err := prefetcher.NewRetryingL2SourcesFromURLs(ctx, logger, cfg.Rollups, cfg.L2URLs, cfg.L2ExperimentalURLs)
if err != nil {
return nil, fmt.Errorf("failed to create L2 sources: %w", err)
}
executor := MakeProgramExecutor(logger, cfg)
return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, cfg.Rollup.L2ChainID.Uint64(), sources, kv, executor, cfg.L2Head, cfg.AgreedPrestate), nil
return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, cfg.Rollups[0].L2ChainID.Uint64(), sources, kv, executor, cfg.L2Head, cfg.AgreedPrestate), nil
}
type programExecutor struct {
......
......@@ -43,12 +43,18 @@ func (s *LocalPreimageSource) Get(key common.Hash) ([]byte, error) {
if s.config.L2ChainID != boot.CustomChainIDIndicator {
return nil, ErrNotFound
}
return json.Marshal(s.config.L2ChainConfig)
if s.config.InteropEnabled {
return json.Marshal(s.config.L2ChainConfigs)
}
return json.Marshal(s.config.L2ChainConfigs[0])
case rollupKey:
if s.config.L2ChainID != boot.CustomChainIDIndicator {
return nil, ErrNotFound
}
return json.Marshal(s.config.Rollup)
if s.config.InteropEnabled {
return json.Marshal(s.config.Rollups)
}
return json.Marshal(s.config.Rollups[0])
default:
return nil, ErrNotFound
}
......
......@@ -3,9 +3,11 @@ package kvstore
import (
"encoding/binary"
"encoding/json"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/rollup"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-program/client/boot"
"github.com/ethereum-optimism/optimism/op-program/host/config"
......@@ -17,12 +19,12 @@ import (
func TestLocalPreimageSource(t *testing.T) {
cfg := &config.Config{
L2ChainID: 86,
Rollup: chaincfg.OPSepolia(),
Rollups: []*rollup.Config{chaincfg.OPSepolia()},
L1Head: common.HexToHash("0x1111"),
L2OutputRoot: common.HexToHash("0x2222"),
L2Claim: common.HexToHash("0x3333"),
L2ClaimBlockNumber: 1234,
L2ChainConfig: params.SepoliaChainConfig,
L2ChainConfigs: []*params.ChainConfig{params.SepoliaChainConfig},
}
source := NewLocalPreimageSource(cfg)
tests := []struct {
......@@ -54,21 +56,43 @@ func TestLocalPreimageSource(t *testing.T) {
func TestGetCustomChainConfigPreimages(t *testing.T) {
cfg := &config.Config{
Rollup: chaincfg.OPSepolia(),
Rollups: []*rollup.Config{chaincfg.OPSepolia()},
L2ChainID: boot.CustomChainIDIndicator,
L1Head: common.HexToHash("0x1111"),
L2OutputRoot: common.HexToHash("0x2222"),
L2Claim: common.HexToHash("0x3333"),
L2ClaimBlockNumber: 1234,
L2ChainConfig: params.SepoliaChainConfig,
L2ChainConfigs: []*params.ChainConfig{params.SepoliaChainConfig},
}
source := NewLocalPreimageSource(cfg)
actualRollup, err := source.Get(rollupKey)
require.NoError(t, err)
require.Equal(t, asJson(t, cfg.Rollup), actualRollup)
require.Equal(t, asJson(t, cfg.Rollups[0]), actualRollup)
actualChainConfig, err := source.Get(l2ChainConfigKey)
require.NoError(t, err)
require.Equal(t, asJson(t, cfg.L2ChainConfig), actualChainConfig)
require.Equal(t, asJson(t, cfg.L2ChainConfigs[0]), actualChainConfig)
}
func TestGetCustomChainConfigPreimagesInterop(t *testing.T) {
rollup2 := &rollup.Config{L2ChainID: big.NewInt(2498)}
chainCfg2 := &params.ChainConfig{ChainID: big.NewInt(2498)}
cfg := &config.Config{
Rollups: []*rollup.Config{chaincfg.OPSepolia(), rollup2},
L2ChainID: boot.CustomChainIDIndicator,
L1Head: common.HexToHash("0x1111"),
L2OutputRoot: common.HexToHash("0x2222"),
L2Claim: common.HexToHash("0x3333"),
L2ClaimBlockNumber: 1234,
L2ChainConfigs: []*params.ChainConfig{params.SepoliaChainConfig, chainCfg2},
InteropEnabled: true,
}
source := NewLocalPreimageSource(cfg)
actualRollup, err := source.Get(rollupKey)
require.NoError(t, err)
require.Equal(t, asJson(t, cfg.Rollups), actualRollup)
actualChainConfig, err := source.Get(l2ChainConfigKey)
require.NoError(t, err)
require.Equal(t, asJson(t, cfg.L2ChainConfigs), actualChainConfig)
}
func asJson(t *testing.T, v any) []byte {
......
......@@ -216,7 +216,7 @@ func (r *Runner) run(ctx context.Context, l1Head common.Hash, agreedBlockInfo et
onlineCfg := *offlineCfg
onlineCfg.L1URL = r.l1RpcUrl
onlineCfg.L1BeaconURL = r.l1BeaconUrl
onlineCfg.L2URL = r.l2RpcUrl
onlineCfg.L2URLs = []string{r.l2RpcUrl}
if r.l1RpcKind != "" {
onlineCfg.L1RPCKind = sources.RPCProviderKind(r.l1RpcKind)
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment