Skip to content

Commit e89248a

Browse files
authored
op-program: Introduce RetryingL2Sources and use it to make prefetcher somewhat multi-L2 capable (#13718)
* op-program: Introduce L2Sources to combine info for multiple L2s. * op-program: Plumb L2Sources in * op-e2e: Use actual chain ID, not hte test place holder. * op-program: Use correct chain ID for rexec and prevent duplicate RPC urls for same chain * op-program: Fix test to use chain ID that is actually configured in the test
1 parent eea9572 commit e89248a

File tree

11 files changed

+429
-35
lines changed

11 files changed

+429
-35
lines changed

op-e2e/actions/proofs/helpers/runner.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import (
55

66
"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
77
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/fakebeacon"
8+
"github.com/ethereum-optimism/optimism/op-node/rollup"
89
"github.com/ethereum-optimism/optimism/op-program/host"
910
hostcommon "github.com/ethereum-optimism/optimism/op-program/host/common"
1011
"github.com/ethereum-optimism/optimism/op-program/host/config"
1112
"github.com/ethereum-optimism/optimism/op-program/host/kvstore"
1213
"github.com/ethereum-optimism/optimism/op-program/host/prefetcher"
14+
"github.com/ethereum-optimism/optimism/op-service/client"
1315
"github.com/ethereum-optimism/optimism/op-service/sources"
1416
"github.com/ethereum/go-ethereum/common"
1517
"github.com/ethereum/go-ethereum/core"
@@ -76,14 +78,11 @@ func RunFaultProofProgram(t helpers.Testing, logger log.Logger, l1 *helpers.L1Mi
7678
l1BlobFetcher := l1.BlobSource()
7779

7880
// Set up in-process L2 source
79-
l2ClCfg := sources.L2ClientDefaultConfig(l2.RollupCfg, true)
80-
l2RPC := l2Eng.RPCClient()
81-
l2Client, err := hostcommon.NewL2Client(l2RPC, logger, nil, &hostcommon.L2ClientConfig{L2ClientConfig: l2ClCfg})
81+
sources, err := prefetcher.NewRetryingL2Sources(ctx, logger, []*rollup.Config{l2.RollupCfg}, []client.RPC{l2Eng.RPCClient()}, nil)
8282
require.NoError(t, err, "failed to create L2 client")
83-
l2DebugCl := hostcommon.NewL2SourceWithClient(logger, l2Client, sources.NewDebugClient(l2RPC.CallContext))
8483

8584
executor := host.MakeProgramExecutor(logger, programCfg)
86-
return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2DebugCl, kv, executor, cfg.L2Head, cfg.AgreedPrestate), nil
85+
return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2.RollupCfg.L2ChainID.Uint64(), sources, kv, executor, cfg.L2Head, cfg.AgreedPrestate), nil
8786
})
8887
err = hostcommon.FaultProofProgram(t.Ctx(), logger, programCfg, withInProcessPrefetcher)
8988
checkResult(t, err)

op-program/host/common/l2_source.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"time"
77

8+
"github.com/ethereum-optimism/optimism/op-node/rollup"
89
"github.com/ethereum-optimism/optimism/op-program/host/config"
910
hosttypes "github.com/ethereum-optimism/optimism/op-program/host/types"
1011
"github.com/ethereum-optimism/optimism/op-service/client"
@@ -50,33 +51,41 @@ func NewL2SourceWithClient(logger log.Logger, canonicalL2Client *L2Client, canon
5051

5152
func NewL2Source(ctx context.Context, logger log.Logger, config *config.Config) (*L2Source, error) {
5253
logger.Info("Connecting to canonical L2 source", "url", config.L2URL)
53-
5454
// eth_getProof calls are expensive and takes time, so we use a longer timeout
5555
canonicalL2RPC, err := client.NewRPC(ctx, logger, config.L2URL, client.WithDialAttempts(10), client.WithCallTimeout(5*time.Minute))
5656
if err != nil {
5757
return nil, err
5858
}
59+
60+
var experimentalRPC client.RPC
61+
62+
if len(config.L2ExperimentalURL) != 0 {
63+
logger.Info("Connecting to experimental L2 source", "url", config.L2ExperimentalURL)
64+
// debug_executionWitness calls are expensive and takes time, so we use a longer timeout
65+
experimentalRPC, err = client.NewRPC(ctx, logger, config.L2ExperimentalURL, client.WithDialAttempts(10), client.WithCallTimeout(5*time.Minute))
66+
if err != nil {
67+
return nil, err
68+
}
69+
}
70+
return NewL2SourceFromRPC(logger, config.Rollup, canonicalL2RPC, experimentalRPC)
71+
}
72+
73+
func NewL2SourceFromRPC(logger log.Logger, rollupCfg *rollup.Config, canonicalL2RPC client.RPC, experimentalRPC client.RPC) (*L2Source, error) {
5974
canonicalDebugClient := sources.NewDebugClient(canonicalL2RPC.CallContext)
6075

61-
canonicalL2ClientCfg := sources.L2ClientDefaultConfig(config.Rollup, true)
76+
canonicalL2ClientCfg := sources.L2ClientDefaultConfig(rollupCfg, true)
6277
canonicalL2Client, err := NewL2Client(canonicalL2RPC, logger, nil, &L2ClientConfig{L2ClientConfig: canonicalL2ClientCfg})
6378
if err != nil {
6479
return nil, err
6580
}
6681

6782
source := NewL2SourceWithClient(logger, canonicalL2Client, canonicalDebugClient)
6883

69-
if len(config.L2ExperimentalURL) == 0 {
84+
if experimentalRPC == nil {
7085
return source, nil
7186
}
7287

73-
logger.Info("Connecting to experimental L2 source", "url", config.L2ExperimentalURL)
74-
// debug_executionWitness calls are expensive and takes time, so we use a longer timeout
75-
experimentalRPC, err := client.NewRPC(ctx, logger, config.L2ExperimentalURL, client.WithDialAttempts(10), client.WithCallTimeout(5*time.Minute))
76-
if err != nil {
77-
return nil, err
78-
}
79-
experimentalL2ClientCfg := sources.L2ClientDefaultConfig(config.Rollup, true)
88+
experimentalL2ClientCfg := sources.L2ClientDefaultConfig(rollupCfg, true)
8089
experimentalL2Client, err := NewL2Client(experimentalRPC, logger, nil, &L2ClientConfig{L2ClientConfig: experimentalL2ClientCfg})
8190
if err != nil {
8291
return nil, err
@@ -87,6 +96,10 @@ func NewL2Source(ctx context.Context, logger log.Logger, config *config.Config)
8796
return source, nil
8897
}
8998

99+
func (s *L2Source) RollupConfig() *rollup.Config {
100+
return s.canonicalEthClient.RollupConfig()
101+
}
102+
90103
func (l *L2Source) ExperimentalEnabled() bool {
91104
return l.experimentalClient != nil
92105
}

op-program/host/host.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66

77
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
8+
"github.com/ethereum-optimism/optimism/op-node/rollup"
89
preimage "github.com/ethereum-optimism/optimism/op-preimage"
910
hostcommon "github.com/ethereum-optimism/optimism/op-program/host/common"
1011
"github.com/ethereum-optimism/optimism/op-program/host/config"
@@ -89,13 +90,17 @@ func makeDefaultPrefetcher(ctx context.Context, logger log.Logger, kv kvstore.KV
8990
l1BlobFetcher := sources.NewL1BeaconClient(l1Beacon, sources.L1BeaconClientConfig{FetchAllSidecars: false})
9091

9192
logger.Info("Initializing L2 clients")
92-
l2Client, err := hostcommon.NewL2Source(ctx, logger, cfg)
93+
var experimentalURLs []string
94+
if cfg.L2ExperimentalURL != "" {
95+
experimentalURLs = append(experimentalURLs, cfg.L2ExperimentalURL)
96+
}
97+
sources, err := prefetcher.NewRetryingL2SourcesFromURLs(ctx, logger, []*rollup.Config{cfg.Rollup}, []string{cfg.L2URL}, experimentalURLs)
9398
if err != nil {
94-
return nil, fmt.Errorf("failed to create L2 source: %w", err)
99+
return nil, fmt.Errorf("failed to create L2 sources: %w", err)
95100
}
96101

97102
executor := MakeProgramExecutor(logger, cfg)
98-
return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2Client, kv, executor, cfg.L2Head, cfg.AgreedPrestate), nil
103+
return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, cfg.Rollup.L2ChainID.Uint64(), sources, kv, executor, cfg.L2Head, cfg.AgreedPrestate), nil
99104
}
100105

101106
type programExecutor struct {
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package prefetcher
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"math/big"
8+
"time"
9+
10+
"github.com/ethereum-optimism/optimism/op-node/rollup"
11+
"github.com/ethereum-optimism/optimism/op-program/host/common"
12+
"github.com/ethereum-optimism/optimism/op-program/host/types"
13+
"github.com/ethereum-optimism/optimism/op-service/client"
14+
"github.com/ethereum/go-ethereum/common/hexutil"
15+
"github.com/ethereum/go-ethereum/log"
16+
)
17+
18+
var (
19+
ErrNoSources = errors.New("no sources specified")
20+
ErrNoL2ForRollup = errors.New("no L2 RPC available for rollup")
21+
ErrNoRollupForL2 = errors.New("no rollup config available for L2 RPC")
22+
ErrDuplicateL2URLs = errors.New("multiple L2 URLs provided for chain")
23+
ErrNoRollupForExperimental = errors.New("no rollup config available for L2 experimental RPC")
24+
ErrDuplicateExperimentsURLs = errors.New("multiple experimental URLs provided for chain")
25+
)
26+
27+
type RetryingL2Sources struct {
28+
Sources map[uint64]*RetryingL2Source
29+
}
30+
31+
func NewRetryingL2SourcesFromURLs(ctx context.Context, logger log.Logger, configs []*rollup.Config, l2URLs []string, l2ExperimentalURLs []string) (*RetryingL2Sources, error) {
32+
l2Clients, err := connectRPCs(ctx, logger, l2URLs)
33+
if err != nil {
34+
return nil, err
35+
}
36+
l2ExperimentalClients, err := connectRPCs(ctx, logger, l2ExperimentalURLs)
37+
if err != nil {
38+
return nil, err
39+
}
40+
return NewRetryingL2Sources(ctx, logger, configs, l2Clients, l2ExperimentalClients)
41+
}
42+
43+
func connectRPCs(ctx context.Context, logger log.Logger, urls []string) ([]client.RPC, error) {
44+
l2Clients := make([]client.RPC, len(urls))
45+
for i, url := range urls {
46+
logger.Info("Connecting to L2 source", "url", url)
47+
// eth_getProof calls are expensive and takes time, so we use a longer timeout
48+
rpc, err := client.NewRPC(ctx, logger, url, client.WithDialAttempts(10), client.WithCallTimeout(5*time.Minute))
49+
if err != nil {
50+
return nil, fmt.Errorf("failed to connect to rpc URL %s: %w", url, err)
51+
}
52+
l2Clients[i] = rpc
53+
}
54+
return l2Clients, nil
55+
}
56+
57+
func NewRetryingL2Sources(ctx context.Context, logger log.Logger, configs []*rollup.Config, l2Clients []client.RPC, l2ExperimentalClients []client.RPC) (*RetryingL2Sources, error) {
58+
if len(configs) == 0 {
59+
return nil, ErrNoSources
60+
}
61+
rollupConfigs := make(map[uint64]*rollup.Config)
62+
for _, rollupCfg := range configs {
63+
rollupConfigs[rollupCfg.L2ChainID.Uint64()] = rollupCfg
64+
}
65+
l2RPCs := make(map[uint64]client.RPC)
66+
for _, rpc := range l2Clients {
67+
chainID, err := loadChainID(ctx, rpc)
68+
if err != nil {
69+
return nil, fmt.Errorf("failed to load chain ID: %w", err)
70+
}
71+
if _, ok := l2RPCs[chainID]; ok {
72+
return nil, fmt.Errorf("%w %v", ErrDuplicateL2URLs, chainID)
73+
}
74+
l2RPCs[chainID] = rpc
75+
if _, ok := rollupConfigs[chainID]; !ok {
76+
return nil, fmt.Errorf("%w: %v", ErrNoRollupForL2, chainID)
77+
}
78+
}
79+
80+
l2ExperimentalRPCs := make(map[uint64]client.RPC)
81+
for _, rpc := range l2ExperimentalClients {
82+
chainID, err := loadChainID(ctx, rpc)
83+
if err != nil {
84+
return nil, fmt.Errorf("failed to load chain ID: %w", err)
85+
}
86+
if _, ok := l2ExperimentalRPCs[chainID]; ok {
87+
return nil, fmt.Errorf("%w %v", ErrDuplicateExperimentsURLs, chainID)
88+
}
89+
l2ExperimentalRPCs[chainID] = rpc
90+
if _, ok := rollupConfigs[chainID]; !ok {
91+
return nil, fmt.Errorf("%w: %v", ErrNoRollupForExperimental, chainID)
92+
}
93+
}
94+
95+
sources := make(map[uint64]*RetryingL2Source, len(configs))
96+
for _, rollupCfg := range rollupConfigs {
97+
chainID := rollupCfg.L2ChainID.Uint64()
98+
l2RPC, ok := l2RPCs[chainID]
99+
if !ok {
100+
return nil, fmt.Errorf("%w: %v", ErrNoL2ForRollup, chainID)
101+
}
102+
l2ExperimentalRPC := l2ExperimentalRPCs[chainID] // Allowed to be nil
103+
source, err := common.NewL2SourceFromRPC(logger, rollupCfg, l2RPC, l2ExperimentalRPC)
104+
if err != nil {
105+
return nil, fmt.Errorf("failed to create l2 source for chain ID %v: %w", chainID, err)
106+
}
107+
sources[chainID] = NewRetryingL2Source(logger, source)
108+
}
109+
110+
return &RetryingL2Sources{
111+
Sources: sources,
112+
}, nil
113+
}
114+
115+
func (s *RetryingL2Sources) ForChainID(chainID uint64) (types.L2Source, error) {
116+
source, ok := s.Sources[chainID]
117+
if !ok {
118+
return nil, fmt.Errorf("no source available for chain ID: %v", chainID)
119+
}
120+
return source, nil
121+
}
122+
123+
func (s *RetryingL2Sources) ForChainIDWithoutRetries(chainID uint64) (types.L2Source, error) {
124+
retrying, ok := s.Sources[chainID]
125+
if !ok {
126+
return nil, fmt.Errorf("no source available for chain ID: %v", chainID)
127+
}
128+
return retrying.source, nil
129+
}
130+
131+
func loadChainID(ctx context.Context, rpc client.RPC) (uint64, error) {
132+
var id hexutil.Big
133+
err := rpc.CallContext(ctx, &id, "eth_chainId")
134+
if err != nil {
135+
return 0, err
136+
}
137+
return (*big.Int)(&id).Uint64(), nil
138+
}

0 commit comments

Comments
 (0)