Skip to content

Commit 27dfa39

Browse files
feat: integrate fault-proof challenger into sysgo (#326)
* feat: integrate fp challenger into sysgo * fix: rm redundant service field * chore: rename interfaces to ***Backend * fix: consistent metrics registration * fix: consistent logging
1 parent 0870bbd commit 27dfa39

File tree

6 files changed

+323
-28
lines changed

6 files changed

+323
-28
lines changed

op-devstack/sysgo/l2_challenger.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ import (
1717
"github.com/ethereum/go-ethereum/crypto"
1818
)
1919

20+
// L2ChallengerBackend is the interface for L2 challengers managed by the orchestrator.
21+
type L2ChallengerBackend interface {
22+
hydrate(system stack.ExtensibleSystem)
23+
}
24+
2025
type l2ChallengerOpts struct {
2126
useCannonKonaConfig bool
2227
}
@@ -28,6 +33,8 @@ type L2Challenger struct {
2833
config *config.Config
2934
}
3035

36+
var _ L2ChallengerBackend = (*L2Challenger)(nil)
37+
3138
func (p *L2Challenger) hydrate(system stack.ExtensibleSystem) {
3239
bFrontend := shim.NewL2Challenger(shim.L2ChallengerConfig{
3340
CommonConfig: shim.NewCommonConfig(system.T()),
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
package sysgo
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"strings"
10+
"sync"
11+
"syscall"
12+
13+
"github.com/ethereum-optimism/optimism/op-chain-ops/devkeys"
14+
"github.com/ethereum-optimism/optimism/op-devstack/devtest"
15+
"github.com/ethereum-optimism/optimism/op-devstack/shim"
16+
"github.com/ethereum-optimism/optimism/op-devstack/stack"
17+
"github.com/ethereum-optimism/optimism/op-service/logpipe"
18+
"github.com/ethereum/go-ethereum/common/hexutil"
19+
"github.com/ethereum/go-ethereum/crypto"
20+
"github.com/ethereum/go-ethereum/log"
21+
)
22+
23+
// L2SuccinctFaultProofChallenger wraps the OP Succinct fault-proof challenger binary as a subprocess.
24+
type L2SuccinctFaultProofChallenger struct {
25+
mu sync.Mutex
26+
id stack.L2ChallengerID
27+
execPath string
28+
args []string
29+
p devtest.P
30+
logger log.Logger
31+
sub *SubProcess
32+
l2MetricsRegistrar L2MetricsRegistrar
33+
metricsPort string
34+
}
35+
36+
var _ L2ChallengerBackend = (*L2SuccinctFaultProofChallenger)(nil)
37+
38+
// FaultProofChallenger extends L2ChallengerBackend with faultproof-specific methods.
39+
type FaultProofChallenger interface {
40+
L2ChallengerBackend
41+
Start()
42+
Stop()
43+
}
44+
45+
var _ FaultProofChallenger = (*L2SuccinctFaultProofChallenger)(nil)
46+
47+
func (c *L2SuccinctFaultProofChallenger) hydrate(system stack.ExtensibleSystem) {
48+
bFrontend := shim.NewL2Challenger(shim.L2ChallengerConfig{
49+
CommonConfig: shim.NewCommonConfig(system.T()),
50+
ID: c.id,
51+
Config: nil, // Succinct challenger runs as subprocess, no op-challenger config
52+
})
53+
l2Net := system.L2Network(stack.L2NetworkID(c.id.ChainID()))
54+
l2Net.(stack.ExtensibleL2Network).AddL2Challenger(bFrontend)
55+
}
56+
57+
// Start starts the fault-proof challenger subprocess.
58+
func (c *L2SuccinctFaultProofChallenger) Start() {
59+
c.mu.Lock()
60+
if c.sub != nil {
61+
c.logger.Warn("Fault Proof Challenger already started")
62+
c.mu.Unlock()
63+
return
64+
}
65+
66+
// We pipe sub-process logs to the test-logger.
67+
logOut := logpipe.ToLogger(c.logger.New("src", "stdout"))
68+
logErr := logpipe.ToLogger(c.logger.New("src", "stderr"))
69+
70+
stdOutLogs := logpipe.LogProcessor(func(line []byte) {
71+
e := logpipe.ParseRustStructuredLogs(line)
72+
logOut(e)
73+
})
74+
stdErrLogs := logpipe.LogProcessor(func(line []byte) {
75+
e := logpipe.ParseRustStructuredLogs(line)
76+
logErr(e)
77+
})
78+
c.sub = NewSubProcess(c.p, stdOutLogs, stdErrLogs)
79+
c.mu.Unlock()
80+
81+
c.sub.OnExit(func(err error) {
82+
if errors.Is(err, syscall.ECHILD) {
83+
return
84+
}
85+
86+
var exitErr *exec.ExitError
87+
if errors.As(err, &exitErr) {
88+
if ws, ok := exitErr.Sys().(syscall.WaitStatus); ok {
89+
sig := ws.Signal()
90+
if sig == syscall.SIGINT || sig == syscall.SIGTERM {
91+
return
92+
}
93+
}
94+
}
95+
96+
c.p.Require().NoError(err, "fault-proof challenger exited unexpectedly")
97+
})
98+
99+
err := c.sub.Start(c.execPath, c.args, []string{})
100+
c.p.Require().NoError(err, "Must start challenger")
101+
102+
if c.metricsPort != "" && c.l2MetricsRegistrar != nil {
103+
metricsTarget := NewPrometheusMetricsTarget("localhost", c.metricsPort, false)
104+
c.l2MetricsRegistrar.RegisterL2MetricsTargets(c.id, metricsTarget)
105+
c.logger.Info("Registered fault-proof challenger metrics", "port", c.metricsPort)
106+
}
107+
}
108+
109+
// Stop stops the fault-proof challenger subprocess.
110+
func (c *L2SuccinctFaultProofChallenger) Stop() {
111+
c.mu.Lock()
112+
defer c.mu.Unlock()
113+
if c.sub == nil {
114+
c.logger.Warn("fault-proof challenger already stopped")
115+
return
116+
}
117+
118+
err := c.sub.Stop(true)
119+
c.p.Require().NoError(err, "Must stop challenger")
120+
c.sub = nil
121+
}
122+
123+
// WithSuccinctFaultProofChallenger creates a fault-proof challenger after deployment.
124+
func WithSuccinctFaultProofChallenger(challengerID stack.L2ChallengerID, l1ELID stack.L1ELNodeID, l2ELID stack.L2ELNodeID, opts ...FaultProofChallengerOption) stack.Option[*Orchestrator] {
125+
return stack.AfterDeploy(func(orch *Orchestrator) {
126+
WithSuccinctFaultProofChallengerPostDeploy(orch, challengerID, l1ELID, l2ELID, opts...)
127+
})
128+
}
129+
130+
// WithSuperSuccinctFaultProofChallenger creates a fault-proof challenger in the Finally phase.
131+
func WithSuperSuccinctFaultProofChallenger(challengerID stack.L2ChallengerID,
132+
l1ELID stack.L1ELNodeID, l2ELID stack.L2ELNodeID, opts ...FaultProofChallengerOption) stack.Option[*Orchestrator] {
133+
return stack.Finally(func(orch *Orchestrator) {
134+
WithSuccinctFaultProofChallengerPostDeploy(orch, challengerID, l1ELID, l2ELID, opts...)
135+
})
136+
}
137+
138+
// WithSuccinctFaultProofChallengerPostDeploy sets up and starts the OP Succinct fault-proof challenger.
139+
func WithSuccinctFaultProofChallengerPostDeploy(orch *Orchestrator, challengerID stack.L2ChallengerID, l1ELID stack.L1ELNodeID, l2ELID stack.L2ELNodeID, opts ...FaultProofChallengerOption) {
140+
ctx := stack.ContextWithID(orch.P().Ctx(), challengerID)
141+
p := orch.P().WithCtx(ctx)
142+
logger := p.Logger().New("component", "succinct-fp-challenger")
143+
144+
require := p.Require()
145+
require.False(orch.challengers.Has(challengerID), "challenger must not already exist")
146+
147+
l2Net, ok := orch.l2Nets.Get(challengerID.ChainID())
148+
require.True(ok, "l2 network required")
149+
150+
l1EL, ok := orch.GetL1EL(l1ELID)
151+
require.True(ok, "l1 EL node required")
152+
153+
l2EL, ok := orch.GetL2EL(l2ELID)
154+
require.True(ok, "l2 EL node required")
155+
156+
// Use ChallengerRole for the challenger key
157+
challengerKey, err := orch.GetKeys().Secret(devkeys.ChallengerRole.Key(challengerID.ChainID().ToBig()))
158+
require.NoError(err, "failed to get challenger key")
159+
challengerKeyStr := hexutil.Encode(crypto.FromECDSA(challengerKey))
160+
161+
cfg := &FaultProofChallengerConfig{}
162+
for _, opt := range opts {
163+
opt(p, challengerID, cfg)
164+
}
165+
166+
l1RPC := l1EL.UserRPC()
167+
l2RPC := strings.ReplaceAll(l2EL.UserRPC(), "ws://", "http://")
168+
anchorStateRegistryAddr := l2Net.deployment.anchorStateRegistry
169+
factoryAddr := l2Net.deployment.disputeGameFactoryProxy
170+
171+
logger.Info("L1_RPC", "url", l1RPC)
172+
logger.Info("L2_RPC", "url", l2RPC)
173+
logger.Info("ANCHOR_STATE_REGISTRY_ADDRESS", "address", anchorStateRegistryAddr)
174+
logger.Info("FACTORY_ADDRESS", "address", factoryAddr)
175+
176+
envVars := map[string]string{
177+
"L1_RPC": l1RPC,
178+
"L2_RPC": l2RPC,
179+
"ANCHOR_STATE_REGISTRY_ADDRESS": anchorStateRegistryAddr.String(),
180+
"FACTORY_ADDRESS": factoryAddr.String(),
181+
"GAME_TYPE": "42",
182+
"PRIVATE_KEY": challengerKeyStr,
183+
"LOG_FORMAT": "json",
184+
}
185+
186+
// Optional parameters (override defaults if set)
187+
setEnvIfNotNil(envVars, "FETCH_INTERVAL", cfg.fetchInterval)
188+
setEnvIfNotNil(envVars, "MALICIOUS_CHALLENGE_PERCENTAGE", cfg.maliciousChallengePercentage)
189+
setEnvIfNotNil(envVars, "RUST_LOG", cfg.rustLog)
190+
191+
var metricsPort string
192+
if areMetricsEnabled() {
193+
metricsPort, err = getAvailableLocalPort()
194+
require.NoError(err, "failed to get available port for challenger metrics")
195+
envVars["CHALLENGER_METRICS_PORT"] = metricsPort
196+
}
197+
198+
envDir := p.TempDir()
199+
envFile := filepath.Join(envDir, fmt.Sprintf("fp-challenger-%s.env", challengerID.String()))
200+
err = WriteEnvFile(envFile, envVars)
201+
p.Require().NoError(err, "must write fault proof challenger env file")
202+
203+
if cfg.envFilePath != nil {
204+
err = WriteEnvFile(*cfg.envFilePath, envVars)
205+
p.Require().NoError(err, "must write challenger env file")
206+
logger.Info("challenger env file written", "path", *cfg.envFilePath)
207+
}
208+
209+
execPath := os.Getenv("FAULT_PROOF_CHALLENGER_EXEC_PATH")
210+
p.Require().NotEmpty(execPath, "FAULT_PROOF_CHALLENGER_EXEC_PATH environment variable must be set")
211+
_, err = os.Stat(execPath)
212+
p.Require().NotErrorIs(err, os.ErrNotExist, "challenger executable must exist")
213+
214+
c := &L2SuccinctFaultProofChallenger{
215+
id: challengerID,
216+
execPath: execPath,
217+
args: []string{"--env-file", envFile},
218+
p: p,
219+
logger: logger,
220+
l2MetricsRegistrar: orch,
221+
metricsPort: metricsPort,
222+
}
223+
logger.Info("Starting fault-proof challenger")
224+
c.Start()
225+
p.Cleanup(func() {
226+
logger.Info("Stopping fault-proof challenger")
227+
c.Stop()
228+
})
229+
logger.Info("fault-proof challenger is running")
230+
231+
// Store the challenger in the orchestrator's challengers map
232+
require.True(orch.challengers.SetIfMissing(challengerID, c), "challenger must not already exist")
233+
}
234+
235+
// FaultProofChallengerConfig holds configuration for the OP Succinct fault-proof challenger.
236+
type FaultProofChallengerConfig struct {
237+
fetchInterval *uint64
238+
maliciousChallengePercentage *float64
239+
rustLog *string
240+
envFilePath *string
241+
}
242+
243+
// FaultProofChallengerOption is a function that configures the FaultProofChallengerConfig.
244+
type FaultProofChallengerOption func(p devtest.P, id stack.L2ChallengerID, cfg *FaultProofChallengerConfig)
245+
246+
// WithFPChallengerFetchInterval sets the polling interval in seconds.
247+
func WithFPChallengerFetchInterval(n uint64) FaultProofChallengerOption {
248+
return FaultProofChallengerOption(func(p devtest.P, id stack.L2ChallengerID, cfg *FaultProofChallengerConfig) {
249+
cfg.fetchInterval = &n
250+
})
251+
}
252+
253+
// WithFPChallengerMaliciousChallengePercentage sets the percentage of valid games to challenge maliciously (for testing).
254+
func WithFPChallengerMaliciousChallengePercentage(pct float64) FaultProofChallengerOption {
255+
return FaultProofChallengerOption(func(p devtest.P, id stack.L2ChallengerID, cfg *FaultProofChallengerConfig) {
256+
cfg.maliciousChallengePercentage = &pct
257+
})
258+
}
259+
260+
// WithFPChallengerRustLog sets the RUST_LOG environment variable.
261+
func WithFPChallengerRustLog(level string) FaultProofChallengerOption {
262+
return FaultProofChallengerOption(func(p devtest.P, id stack.L2ChallengerID, cfg *FaultProofChallengerConfig) {
263+
cfg.rustLog = &level
264+
})
265+
}
266+
267+
// WithFPChallengerWriteEnvFile enables writing environment variables to a file.
268+
func WithFPChallengerWriteEnvFile(path string) FaultProofChallengerOption {
269+
return FaultProofChallengerOption(func(p devtest.P, id stack.L2ChallengerID, cfg *FaultProofChallengerConfig) {
270+
cfg.envFilePath = &path
271+
})
272+
}

op-devstack/sysgo/l2_proposer.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ import (
2121
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
2222
)
2323

24-
type L2Prop interface {
24+
// L2ProposerBackend is the interface for L2 proposers managed by the orchestrator.
25+
type L2ProposerBackend interface {
2526
hydrate(system stack.ExtensibleSystem)
2627
UserRPC() string
2728
}
2829

29-
var _ L2Prop = (*L2Proposer)(nil)
30+
var _ L2ProposerBackend = (*L2Proposer)(nil)
3031

3132
type L2Proposer struct {
3233
id stack.L2ProposerID

op-devstack/sysgo/l2_proposer_faultproof.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/ethereum-optimism/optimism/op-devstack/devtest"
1515
"github.com/ethereum-optimism/optimism/op-devstack/shim"
1616
"github.com/ethereum-optimism/optimism/op-devstack/stack"
17-
ps "github.com/ethereum-optimism/optimism/op-proposer/proposer"
1817
"github.com/ethereum-optimism/optimism/op-service/client"
1918
"github.com/ethereum-optimism/optimism/op-service/logpipe"
2019
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -25,21 +24,21 @@ import (
2524
type L2SuccinctFaultProofProposer struct {
2625
mu sync.Mutex
2726
id stack.L2ProposerID
28-
service *ps.ProposerService
2927
userRPC string
3028
execPath string
3129
args []string
3230
p devtest.P
3331
logger log.Logger
3432
sub *SubProcess
3533
l2MetricsRegistrar L2MetricsRegistrar
34+
metricsPort string
3635
}
3736

38-
var _ L2Prop = (*L2SuccinctFaultProofProposer)(nil)
37+
var _ L2ProposerBackend = (*L2SuccinctFaultProofProposer)(nil)
3938

40-
// FaultProofProposer extends L2Prop with faultproof-specific methods.
39+
// FaultProofProposer extends L2ProposerBackend with faultproof-specific methods.
4140
type FaultProofProposer interface {
42-
L2Prop
41+
L2ProposerBackend
4342
Start()
4443
Stop()
4544
}
@@ -109,6 +108,12 @@ func (k *L2SuccinctFaultProofProposer) Start() {
109108

110109
err := k.sub.Start(k.execPath, k.args, []string{})
111110
k.p.Require().NoError(err, "Must start")
111+
112+
if k.metricsPort != "" && k.l2MetricsRegistrar != nil {
113+
metricsTarget := NewPrometheusMetricsTarget("localhost", k.metricsPort, false)
114+
k.l2MetricsRegistrar.RegisterL2MetricsTargets(k.id, metricsTarget)
115+
k.logger.Info("Registered fault-proof proposer metrics", "port", k.metricsPort)
116+
}
112117
}
113118

114119
// Stops the fault-proof proposer.
@@ -141,7 +146,7 @@ func WithSuperSuccinctFaultProofProposer(proposerID stack.L2ProposerID,
141146
func WithSuccinctFaultProofProposerPostDeploy(orch *Orchestrator, proposerID stack.L2ProposerID, l1CLID stack.L1CLNodeID, l1ELID stack.L1ELNodeID, l2CLID stack.L2CLNodeID, l2ELID stack.L2ELNodeID, opts ...FaultProofProposerOption) {
142147
ctx := stack.ContextWithID(orch.P().Ctx(), proposerID)
143148
p := orch.P().WithCtx(ctx)
144-
logger := p.Logger().New("component", "succinct-faultproof")
149+
logger := p.Logger().New("component", "succinct-fp-proposer")
145150

146151
require := p.Require()
147152
require.False(orch.proposers.Has(proposerID), "proposer must not already exist")
@@ -226,17 +231,15 @@ func WithSuccinctFaultProofProposerPostDeploy(orch *Orchestrator, proposerID sta
226231
setEnvIfNotNil(envVars, "MOCK_MODE", cfg.mockMode)
227232
setEnvIfNotNil(envVars, "RUST_LOG", cfg.rustLog)
228233

234+
var metricsPort string
229235
if areMetricsEnabled() {
230-
metricsPort, err := getAvailableLocalPort()
231-
require.NoError(err, "failed to get available port for metrics")
236+
metricsPort, err = getAvailableLocalPort()
237+
require.NoError(err, "failed to get available port for proposer metrics")
232238
envVars["PROPOSER_METRICS_PORT"] = metricsPort
233-
metricsTarget := NewPrometheusMetricsTarget("localhost", metricsPort, false)
234-
orch.RegisterL2MetricsTargets(proposerID, metricsTarget)
235-
logger.Info("Registered fault-proof proposer metrics", "port", metricsPort)
236239
}
237240

238241
envDir := p.TempDir()
239-
envFile := filepath.Join(envDir, fmt.Sprintf("fault-proof-proposer-%s.env", proposerID.String()))
242+
envFile := filepath.Join(envDir, fmt.Sprintf("fp-proposer-%s.env", proposerID.String()))
240243
err = WriteEnvFile(envFile, envVars)
241244
p.Require().NoError(err, "must write fault proof proposer env file")
242245

@@ -259,6 +262,7 @@ func WithSuccinctFaultProofProposerPostDeploy(orch *Orchestrator, proposerID sta
259262
p: p,
260263
logger: logger,
261264
l2MetricsRegistrar: orch,
265+
metricsPort: metricsPort,
262266
}
263267
logger.Info("Starting fault-proof proposer")
264268
k.Start()

0 commit comments

Comments
 (0)