Skip to content

Commit 5e3fc2e

Browse files
authored
feat(l3s): Add support for the default finality lookback (#18071)
1 parent ebc2a73 commit 5e3fc2e

File tree

9 files changed

+189
-20
lines changed

9 files changed

+189
-20
lines changed

op-e2e/actions/helpers/l2_verifier.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,9 @@ func NewL2Verifier(t Testing, log log.Logger, l1 derive.L1Fetcher,
155155

156156
var finalizer driver.Finalizer
157157
if cfg.AltDAEnabled() {
158-
finalizer = finality.NewAltDAFinalizer(ctx, log, cfg, l1, altDASrc, ec)
158+
finalizer = finality.NewAltDAFinalizer(ctx, log, cfg, nil, l1, altDASrc, ec)
159159
} else {
160-
finalizer = finality.NewFinalizer(ctx, log, cfg, l1, ec)
160+
finalizer = finality.NewFinalizer(ctx, log, cfg, nil, l1, ec)
161161
}
162162
sys.Register("finalizer", finalizer, opts)
163163

op-node/flags/flags.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,18 @@ var (
252252
Value: false,
253253
Category: SequencerCategory,
254254
}
255+
FinalityLookbackFlag = &cli.Uint64Flag{
256+
Name: "finality.lookback",
257+
Usage: "Number of L1 blocks to look back for finality verification. Uses default calculation if 0 (considers alt-DA challenge/resolve windows if applicable).",
258+
EnvVars: prefixEnvVars("FINALITY_LOOKBACK"),
259+
Category: RollupCategory,
260+
}
261+
FinalityDelayFlag = &cli.Uint64Flag{
262+
Name: "finality.delay",
263+
Usage: "Number of L1 blocks to traverse before trying to finalize L2 blocks again. Uses default (64) if 0.",
264+
EnvVars: prefixEnvVars("FINALITY_DELAY"),
265+
Category: RollupCategory,
266+
}
255267
L1EpochPollIntervalFlag = &cli.DurationFlag{
256268
Name: "l1.epoch-poll-interval",
257269
Usage: "Poll interval for retrieving new L1 epoch updates such as safe and finalized block changes. Disabled if 0 or negative.",
@@ -450,6 +462,8 @@ var optionalFlags = []cli.Flag{
450462
SequencerMaxSafeLagFlag,
451463
SequencerL1Confs,
452464
SequencerRecoverMode,
465+
FinalityLookbackFlag,
466+
FinalityDelayFlag,
453467
L1EpochPollIntervalFlag,
454468
RuntimeConfigReloadIntervalFlag,
455469
RPCAdminPersistence,

op-node/rollup/driver/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package driver
22

3+
import "github.com/ethereum-optimism/optimism/op-node/rollup/finality"
4+
35
type Config struct {
46
// VerifierConfDepth is the distance to keep from the L1 head when reading L1 data for L2 derivation.
57
VerifierConfDepth uint64 `json:"verifier_conf_depth"`
@@ -24,4 +26,7 @@ type Config struct {
2426
// RecoverMode forces the sequencer to select the next L1 Origin exactly, and create an empty block,
2527
// to be compatible with verifiers forcefully generating the same block while catching up the sequencing window timeout.
2628
RecoverMode bool `json:"recover_mode"`
29+
30+
// Finalizer contains runtime configuration for finality behavior.
31+
Finalizer *finality.Config `json:"finalizer,omitempty"`
2732
}

op-node/rollup/driver/driver.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ func NewDriver(
6565

6666
var finalizer Finalizer
6767
if cfg.AltDAEnabled() {
68-
finalizer = finality.NewAltDAFinalizer(driverCtx, log, cfg, l1, altDA, ec)
68+
finalizer = finality.NewAltDAFinalizer(driverCtx, log, cfg, driverCfg.Finalizer, l1, altDA, ec)
6969
} else {
70-
finalizer = finality.NewFinalizer(driverCtx, log, cfg, l1, ec)
70+
finalizer = finality.NewFinalizer(driverCtx, log, cfg, driverCfg.Finalizer, l1, ec)
7171
}
7272
sys.Register("finalizer", finalizer)
7373

op-node/rollup/finality/altda.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ type AltDAFinalizer struct {
2727
backend AltDABackend
2828
}
2929

30-
func NewAltDAFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config,
30+
// NewAltDAFinalizer creates a new AltDAFinalizer instance.
31+
// The finalizerCfg parameter is optional and may be nil to use default finality behavior.
32+
// When non-nil, any non-nil fields in finalizerCfg will override the defaults.
33+
func NewAltDAFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config, finalizerCfg *Config,
3134
l1Fetcher FinalizerL1Interface,
3235
backend AltDABackend, ec EngineController) *AltDAFinalizer {
3336

34-
inner := NewFinalizer(ctx, log, cfg, l1Fetcher, ec)
37+
inner := NewFinalizer(ctx, log, cfg, finalizerCfg, l1Fetcher, ec)
3538

3639
// In alt-da mode, the finalization signal is proxied through the AltDA manager.
3740
// Finality signal will come from the DA contract or L1 finality whichever is last.

op-node/rollup/finality/altda_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ func TestAltDAFinalityData(t *testing.T) {
8181
DAResolveWindow: 90,
8282
}
8383
// should return l1 finality if altda is not enabled
84-
require.Equal(t, uint64(defaultFinalityLookback), calcFinalityLookback(cfg))
84+
require.Equal(t, uint64(defaultFinalityLookback), calcFinalityLookback(cfg, nil))
8585

8686
cfg.AltDAConfig = altDACfg
8787
expFinalityLookback := 181
88-
require.Equal(t, uint64(expFinalityLookback), calcFinalityLookback(cfg))
88+
require.Equal(t, uint64(expFinalityLookback), calcFinalityLookback(cfg, nil))
8989

9090
refA1 := eth.L2BlockRef{
9191
Hash: testutils.RandomHash(rng),
@@ -108,7 +108,7 @@ func TestAltDAFinalityData(t *testing.T) {
108108

109109
emitter := &testutils.MockEmitter{}
110110
ec := new(fakeEngineController)
111-
fi := NewAltDAFinalizer(context.Background(), logger, cfg, l1F, altDABackend, ec)
111+
fi := NewAltDAFinalizer(context.Background(), logger, cfg, nil, l1F, altDABackend, ec)
112112
fi.AttachEmitter(emitter)
113113
require.NotNil(t, altDABackend.forwardTo, "altda backend must have access to underlying standard finalizer")
114114

op-node/rollup/finality/finalizer.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,26 @@ const defaultFinalityLookback = 4*32 + 1
3434
// We do not want to do this too often, since it requires fetching a L1 block by number, so no cache data.
3535
const finalityDelay = 64
3636

37+
// Config contains runtime configuration for the finalizer.
38+
type Config struct {
39+
// FinalityLookback specifies the number of L1 blocks to look back for finality verification.
40+
// When nil, uses the default finality lookback calculation (which considers both
41+
// the default lookback and alt-DA challenge/resolve windows if applicable).
42+
FinalityLookback *uint64
43+
44+
// FinalityDelay specifies the number of L1 blocks to traverse before trying to finalize L2 blocks again.
45+
// When nil, defaults to 64 blocks.
46+
FinalityDelay *uint64
47+
}
48+
3749
// calcFinalityLookback calculates the default finality lookback based on DA challenge window if altDA
3850
// mode is activated or L1 finality lookback.
39-
func calcFinalityLookback(cfg *rollup.Config) uint64 {
51+
func calcFinalityLookback(cfg *rollup.Config, finalizerCfg *Config) uint64 {
52+
// If a custom finality lookback is configured, use it as an override
53+
if finalizerCfg != nil && finalizerCfg.FinalityLookback != nil {
54+
return *finalizerCfg.FinalityLookback
55+
}
56+
4057
// in alt-da mode the longest finality lookback is a commitment is challenged on the last block of
4158
// the challenge window in which case it will be both challenge + resolve window.
4259
if cfg.AltDAEnabled() {
@@ -49,6 +66,15 @@ func calcFinalityLookback(cfg *rollup.Config) uint64 {
4966
return defaultFinalityLookback
5067
}
5168

69+
// calcFinalityDelay calculates the finality delay based on the runtime config or returns the default.
70+
func calcFinalityDelay(finalizerCfg *Config) uint64 {
71+
// If a custom finality delay is configured, use it as an override
72+
if finalizerCfg != nil && finalizerCfg.FinalityDelay != nil {
73+
return *finalizerCfg.FinalityDelay
74+
}
75+
return finalityDelay
76+
}
77+
5278
type FinalityData struct {
5379
// The last L2 block that was fully derived and inserted into the L2 engine while processing this L1 block.
5480
L2Block eth.L2BlockRef
@@ -99,11 +125,18 @@ type Finalizer struct {
99125
// Maximum amount of L2 blocks to store in finalityData.
100126
finalityLookback uint64
101127

128+
// Number of L1 blocks to traverse before trying to finalize L2 blocks again.
129+
finalityDelay uint64
130+
102131
l1Fetcher FinalizerL1Interface
103132
}
104133

105-
func NewFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config, l1Fetcher FinalizerL1Interface, ec EngineController) *Finalizer {
106-
lookback := calcFinalityLookback(cfg)
134+
// NewFinalizer creates a new Finalizer instance.
135+
// The finalizerCfg parameter is optional and may be nil to use default finality behavior.
136+
// When non-nil, any non-nil fields in finalizerCfg will override the defaults.
137+
func NewFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config, finalizerCfg *Config, l1Fetcher FinalizerL1Interface, ec EngineController) *Finalizer {
138+
lookback := calcFinalityLookback(cfg, finalizerCfg)
139+
delay := calcFinalityDelay(finalizerCfg)
107140
return &Finalizer{
108141
ctx: ctx,
109142
cfg: cfg,
@@ -113,6 +146,7 @@ func NewFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config, l1Fet
113146
triedFinalizeAt: 0,
114147
finalityData: make([]FinalityData, 0, lookback),
115148
finalityLookback: lookback,
149+
finalityDelay: delay,
116150
l1Fetcher: l1Fetcher,
117151
}
118152
}
@@ -195,7 +229,7 @@ func (fi *Finalizer) onDerivationIdle(derivedFrom eth.L1BlockRef) {
195229
return // if no L1 information is finalized yet, then skip this
196230
}
197231
// If we recently tried finalizing, then don't try again just yet, but traverse more of L1 first.
198-
if fi.triedFinalizeAt != 0 && derivedFrom.Number <= fi.triedFinalizeAt+finalityDelay {
232+
if fi.triedFinalizeAt != 0 && derivedFrom.Number <= fi.triedFinalizeAt+fi.finalityDelay {
199233
return
200234
}
201235
fi.log.Debug("processing L1 finality information", "l1_finalized", fi.finalizedL1, "derived_from", derivedFrom, "previous", fi.triedFinalizeAt)

op-node/rollup/finality/finalizer_test.go

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ func TestEngineQueue_Finalize(t *testing.T) {
194194

195195
emitter := &testutils.MockEmitter{}
196196
ec := new(fakeEngineController)
197-
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, ec)
197+
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, nil, l1F, ec)
198198
fi.AttachEmitter(emitter)
199199

200200
// now say C1 was included in D and became the new safe head
@@ -229,7 +229,7 @@ func TestEngineQueue_Finalize(t *testing.T) {
229229

230230
emitter := &testutils.MockEmitter{}
231231
ec := new(fakeEngineController)
232-
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, ec)
232+
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, nil, l1F, ec)
233233
fi.AttachEmitter(emitter)
234234

235235
// now say C1 was included in D and became the new safe head
@@ -268,7 +268,7 @@ func TestEngineQueue_Finalize(t *testing.T) {
268268

269269
emitter := &testutils.MockEmitter{}
270270
ec := new(fakeEngineController)
271-
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, ec)
271+
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, nil, l1F, ec)
272272
fi.AttachEmitter(emitter)
273273

274274
fi.OnEvent(ctx, engine.SafeDerivedEvent{Safe: refC1, Source: refD})
@@ -352,7 +352,7 @@ func TestEngineQueue_Finalize(t *testing.T) {
352352

353353
emitter := &testutils.MockEmitter{}
354354
ec := new(fakeEngineController)
355-
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, ec)
355+
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, nil, l1F, ec)
356356
fi.AttachEmitter(emitter)
357357

358358
// now say B1 was included in C and became the new safe head
@@ -389,7 +389,7 @@ func TestEngineQueue_Finalize(t *testing.T) {
389389

390390
emitter := &testutils.MockEmitter{}
391391
ec := new(fakeEngineController)
392-
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, ec)
392+
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, nil, l1F, ec)
393393
fi.AttachEmitter(emitter)
394394

395395
// now say B1 was included in C and became the new safe head
@@ -486,7 +486,7 @@ func TestEngineQueue_Finalize(t *testing.T) {
486486
ec := new(fakeEngineController)
487487
fi := NewFinalizer(context.Background(), logger, &rollup.Config{
488488
InteropTime: &refC1.Time,
489-
}, l1F, ec)
489+
}, nil, l1F, ec)
490490
fi.AttachEmitter(emitter)
491491

492492
// now say C0 and C1 were included in D and became the new safe head
@@ -504,3 +504,101 @@ func TestEngineQueue_Finalize(t *testing.T) {
504504
emitter.AssertExpectations(t)
505505
})
506506
}
507+
508+
func TestFinalizerConfig(t *testing.T) {
509+
t.Run("uses custom finality lookback", func(t *testing.T) {
510+
logger := testlog.Logger(t, log.LevelError)
511+
l1F := &testutils.MockL1Source{}
512+
ec := new(fakeEngineController)
513+
514+
customLookback := uint64(200)
515+
finalizerCfg := &Config{
516+
FinalityLookback: &customLookback,
517+
}
518+
519+
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, finalizerCfg, l1F, ec)
520+
521+
require.Equal(t, customLookback, fi.finalityLookback, "should use custom finality lookback")
522+
require.Equal(t, int(customLookback), cap(fi.finalityData), "finalityData capacity should match custom lookback")
523+
})
524+
525+
t.Run("uses custom finality delay", func(t *testing.T) {
526+
logger := testlog.Logger(t, log.LevelError)
527+
l1F := &testutils.MockL1Source{}
528+
ec := new(fakeEngineController)
529+
530+
customDelay := uint64(32)
531+
finalizerCfg := &Config{
532+
FinalityDelay: &customDelay,
533+
}
534+
535+
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, finalizerCfg, l1F, ec)
536+
537+
require.Equal(t, customDelay, fi.finalityDelay, "should use custom finality delay")
538+
})
539+
540+
t.Run("uses defaults when config is nil", func(t *testing.T) {
541+
logger := testlog.Logger(t, log.LevelError)
542+
l1F := &testutils.MockL1Source{}
543+
ec := new(fakeEngineController)
544+
545+
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, nil, l1F, ec)
546+
547+
require.Equal(t, uint64(defaultFinalityLookback), fi.finalityLookback, "should use default finality lookback when config is nil")
548+
require.Equal(t, uint64(finalityDelay), fi.finalityDelay, "should use default finality delay when config is nil")
549+
})
550+
551+
t.Run("uses defaults when config fields are nil", func(t *testing.T) {
552+
logger := testlog.Logger(t, log.LevelError)
553+
l1F := &testutils.MockL1Source{}
554+
ec := new(fakeEngineController)
555+
556+
// Passing empty config should behave same as nil
557+
finalizerCfg := &Config{}
558+
559+
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, finalizerCfg, l1F, ec)
560+
561+
require.Equal(t, uint64(defaultFinalityLookback), fi.finalityLookback, "should use default finality lookback when config fields are nil")
562+
require.Equal(t, uint64(finalityDelay), fi.finalityDelay, "should use default finality delay when config fields are nil")
563+
})
564+
565+
t.Run("uses alt-da lookback when configured", func(t *testing.T) {
566+
logger := testlog.Logger(t, log.LevelError)
567+
l1F := &testutils.MockL1Source{}
568+
ec := new(fakeEngineController)
569+
570+
cfg := &rollup.Config{
571+
AltDAConfig: &rollup.AltDAConfig{
572+
DAChallengeWindow: 90,
573+
DAResolveWindow: 90,
574+
},
575+
}
576+
577+
fi := NewFinalizer(context.Background(), logger, cfg, nil, l1F, ec)
578+
579+
expectedLookback := uint64(181) // 90 + 90 + 1
580+
require.Equal(t, expectedLookback, fi.finalityLookback, "should use alt-da calculated lookback")
581+
})
582+
583+
t.Run("custom lookback overrides alt-da calculation", func(t *testing.T) {
584+
logger := testlog.Logger(t, log.LevelError)
585+
l1F := &testutils.MockL1Source{}
586+
ec := new(fakeEngineController)
587+
588+
cfg := &rollup.Config{
589+
AltDAConfig: &rollup.AltDAConfig{
590+
DAChallengeWindow: 90,
591+
DAResolveWindow: 90,
592+
},
593+
}
594+
595+
customLookback := uint64(300)
596+
finalizerCfg := &Config{
597+
FinalityLookback: &customLookback,
598+
}
599+
600+
fi := NewFinalizer(context.Background(), logger, cfg, finalizerCfg, l1F, ec)
601+
602+
require.Equal(t, customLookback, fi.finalityLookback, "custom lookback should override alt-da calculation")
603+
})
604+
}

op-node/service.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/ethereum-optimism/optimism/op-node/rollup"
2222
"github.com/ethereum-optimism/optimism/op-node/rollup/driver"
2323
"github.com/ethereum-optimism/optimism/op-node/rollup/engine"
24+
"github.com/ethereum-optimism/optimism/op-node/rollup/finality"
2425
"github.com/ethereum-optimism/optimism/op-node/rollup/interop"
2526
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
2627
"github.com/ethereum-optimism/optimism/op-service/cliiface"
@@ -202,14 +203,28 @@ func NewConfigPersistence(ctx cliiface.Context) config.ConfigPersistence {
202203
}
203204

204205
func NewDriverConfig(ctx cliiface.Context) *driver.Config {
205-
return &driver.Config{
206+
cfg := &driver.Config{
206207
VerifierConfDepth: ctx.Uint64(flags.VerifierL1Confs.Name),
207208
SequencerConfDepth: ctx.Uint64(flags.SequencerL1Confs.Name),
208209
SequencerEnabled: ctx.Bool(flags.SequencerEnabledFlag.Name),
209210
SequencerStopped: ctx.Bool(flags.SequencerStoppedFlag.Name),
210211
SequencerMaxSafeLag: ctx.Uint64(flags.SequencerMaxSafeLagFlag.Name),
211212
RecoverMode: ctx.Bool(flags.SequencerRecoverMode.Name),
212213
}
214+
215+
// Populate finality config from flags. A finality config with null fields
216+
// is handled the same way as a null finality config.
217+
cfg.Finalizer = &finality.Config{}
218+
if ctx.IsSet(flags.FinalityLookbackFlag.Name) {
219+
lookback := ctx.Uint64(flags.FinalityLookbackFlag.Name)
220+
cfg.Finalizer.FinalityLookback = &lookback
221+
}
222+
if ctx.IsSet(flags.FinalityDelayFlag.Name) {
223+
delay := ctx.Uint64(flags.FinalityDelayFlag.Name)
224+
cfg.Finalizer.FinalityDelay = &delay
225+
}
226+
227+
return cfg
213228
}
214229

215230
func NewRollupConfigFromCLI(log log.Logger, ctx cliiface.Context) (*rollup.Config, error) {

0 commit comments

Comments
 (0)