Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions op-e2e/actions/helpers/l2_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,9 @@ func NewL2Verifier(t Testing, log log.Logger, l1 derive.L1Fetcher,

var finalizer driver.Finalizer
if cfg.AltDAEnabled() {
finalizer = finality.NewAltDAFinalizer(ctx, log, cfg, l1, altDASrc, ec)
finalizer = finality.NewAltDAFinalizer(ctx, log, cfg, nil, l1, altDASrc, ec)
} else {
finalizer = finality.NewFinalizer(ctx, log, cfg, l1, ec)
finalizer = finality.NewFinalizer(ctx, log, cfg, nil, l1, ec)
}
sys.Register("finalizer", finalizer, opts)

Expand Down
14 changes: 14 additions & 0 deletions op-node/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,18 @@ var (
Value: false,
Category: SequencerCategory,
}
FinalityLookbackFlag = &cli.Uint64Flag{
Name: "finality.lookback",
Usage: "Number of L1 blocks to look back for finality verification. Uses default calculation if 0 (considers alt-DA challenge/resolve windows if applicable).",
EnvVars: prefixEnvVars("FINALITY_LOOKBACK"),
Category: RollupCategory,
}
FinalityDelayFlag = &cli.Uint64Flag{
Name: "finality.delay",
Usage: "Number of L1 blocks to traverse before trying to finalize L2 blocks again. Uses default (64) if 0.",
EnvVars: prefixEnvVars("FINALITY_DELAY"),
Category: RollupCategory,
}
L1EpochPollIntervalFlag = &cli.DurationFlag{
Name: "l1.epoch-poll-interval",
Usage: "Poll interval for retrieving new L1 epoch updates such as safe and finalized block changes. Disabled if 0 or negative.",
Expand Down Expand Up @@ -450,6 +462,8 @@ var optionalFlags = []cli.Flag{
SequencerMaxSafeLagFlag,
SequencerL1Confs,
SequencerRecoverMode,
FinalityLookbackFlag,
FinalityDelayFlag,
L1EpochPollIntervalFlag,
RuntimeConfigReloadIntervalFlag,
RPCAdminPersistence,
Expand Down
5 changes: 5 additions & 0 deletions op-node/rollup/driver/config.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package driver

import "github.com/ethereum-optimism/optimism/op-node/rollup/finality"

type Config struct {
// VerifierConfDepth is the distance to keep from the L1 head when reading L1 data for L2 derivation.
VerifierConfDepth uint64 `json:"verifier_conf_depth"`
Expand All @@ -24,4 +26,7 @@ type Config struct {
// RecoverMode forces the sequencer to select the next L1 Origin exactly, and create an empty block,
// to be compatible with verifiers forcefully generating the same block while catching up the sequencing window timeout.
RecoverMode bool `json:"recover_mode"`

// Finalizer contains runtime configuration for finality behavior.
Finalizer *finality.Config `json:"finalizer,omitempty"`
}
4 changes: 2 additions & 2 deletions op-node/rollup/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ func NewDriver(

var finalizer Finalizer
if cfg.AltDAEnabled() {
finalizer = finality.NewAltDAFinalizer(driverCtx, log, cfg, l1, altDA, ec)
finalizer = finality.NewAltDAFinalizer(driverCtx, log, cfg, driverCfg.Finalizer, l1, altDA, ec)
} else {
finalizer = finality.NewFinalizer(driverCtx, log, cfg, l1, ec)
finalizer = finality.NewFinalizer(driverCtx, log, cfg, driverCfg.Finalizer, l1, ec)
}
sys.Register("finalizer", finalizer)

Expand Down
7 changes: 5 additions & 2 deletions op-node/rollup/finality/altda.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ type AltDAFinalizer struct {
backend AltDABackend
}

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

inner := NewFinalizer(ctx, log, cfg, l1Fetcher, ec)
inner := NewFinalizer(ctx, log, cfg, finalizerCfg, l1Fetcher, ec)

// In alt-da mode, the finalization signal is proxied through the AltDA manager.
// Finality signal will come from the DA contract or L1 finality whichever is last.
Expand Down
6 changes: 3 additions & 3 deletions op-node/rollup/finality/altda_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,11 @@ func TestAltDAFinalityData(t *testing.T) {
DAResolveWindow: 90,
}
// should return l1 finality if altda is not enabled
require.Equal(t, uint64(defaultFinalityLookback), calcFinalityLookback(cfg))
require.Equal(t, uint64(defaultFinalityLookback), calcFinalityLookback(cfg, nil))

cfg.AltDAConfig = altDACfg
expFinalityLookback := 181
require.Equal(t, uint64(expFinalityLookback), calcFinalityLookback(cfg))
require.Equal(t, uint64(expFinalityLookback), calcFinalityLookback(cfg, nil))

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

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

Expand Down
42 changes: 38 additions & 4 deletions op-node/rollup/finality/finalizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,26 @@ const defaultFinalityLookback = 4*32 + 1
// We do not want to do this too often, since it requires fetching a L1 block by number, so no cache data.
const finalityDelay = 64

// Config contains runtime configuration for the finalizer.
type Config struct {
// FinalityLookback specifies the number of L1 blocks to look back for finality verification.
// When nil, uses the default finality lookback calculation (which considers both
// the default lookback and alt-DA challenge/resolve windows if applicable).
FinalityLookback *uint64

// FinalityDelay specifies the number of L1 blocks to traverse before trying to finalize L2 blocks again.
// When nil, defaults to 64 blocks.
FinalityDelay *uint64
}

// calcFinalityLookback calculates the default finality lookback based on DA challenge window if altDA
// mode is activated or L1 finality lookback.
func calcFinalityLookback(cfg *rollup.Config) uint64 {
func calcFinalityLookback(cfg *rollup.Config, finalizerCfg *Config) uint64 {
// If a custom finality lookback is configured, use it as an override
if finalizerCfg != nil && finalizerCfg.FinalityLookback != nil {
return *finalizerCfg.FinalityLookback
}

// in alt-da mode the longest finality lookback is a commitment is challenged on the last block of
// the challenge window in which case it will be both challenge + resolve window.
if cfg.AltDAEnabled() {
Expand All @@ -49,6 +66,15 @@ func calcFinalityLookback(cfg *rollup.Config) uint64 {
return defaultFinalityLookback
}

// calcFinalityDelay calculates the finality delay based on the runtime config or returns the default.
func calcFinalityDelay(finalizerCfg *Config) uint64 {
// If a custom finality delay is configured, use it as an override
if finalizerCfg != nil && finalizerCfg.FinalityDelay != nil {
return *finalizerCfg.FinalityDelay
}
return finalityDelay
}

type FinalityData struct {
// The last L2 block that was fully derived and inserted into the L2 engine while processing this L1 block.
L2Block eth.L2BlockRef
Expand Down Expand Up @@ -99,11 +125,18 @@ type Finalizer struct {
// Maximum amount of L2 blocks to store in finalityData.
finalityLookback uint64

// Number of L1 blocks to traverse before trying to finalize L2 blocks again.
finalityDelay uint64

l1Fetcher FinalizerL1Interface
}

func NewFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config, l1Fetcher FinalizerL1Interface, ec EngineController) *Finalizer {
lookback := calcFinalityLookback(cfg)
// NewFinalizer creates a new Finalizer instance.
// The finalizerCfg parameter is optional and may be nil to use default finality behavior.
// When non-nil, any non-nil fields in finalizerCfg will override the defaults.
func NewFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config, finalizerCfg *Config, l1Fetcher FinalizerL1Interface, ec EngineController) *Finalizer {
lookback := calcFinalityLookback(cfg, finalizerCfg)
delay := calcFinalityDelay(finalizerCfg)
return &Finalizer{
ctx: ctx,
cfg: cfg,
Expand All @@ -113,6 +146,7 @@ func NewFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config, l1Fet
triedFinalizeAt: 0,
finalityData: make([]FinalityData, 0, lookback),
finalityLookback: lookback,
finalityDelay: delay,
l1Fetcher: l1Fetcher,
}
}
Expand Down Expand Up @@ -195,7 +229,7 @@ func (fi *Finalizer) onDerivationIdle(derivedFrom eth.L1BlockRef) {
return // if no L1 information is finalized yet, then skip this
}
// If we recently tried finalizing, then don't try again just yet, but traverse more of L1 first.
if fi.triedFinalizeAt != 0 && derivedFrom.Number <= fi.triedFinalizeAt+finalityDelay {
if fi.triedFinalizeAt != 0 && derivedFrom.Number <= fi.triedFinalizeAt+fi.finalityDelay {
return
}
fi.log.Debug("processing L1 finality information", "l1_finalized", fi.finalizedL1, "derived_from", derivedFrom, "previous", fi.triedFinalizeAt)
Expand Down
110 changes: 104 additions & 6 deletions op-node/rollup/finality/finalizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func TestEngineQueue_Finalize(t *testing.T) {

emitter := &testutils.MockEmitter{}
ec := new(fakeEngineController)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, ec)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, nil, l1F, ec)
fi.AttachEmitter(emitter)

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

emitter := &testutils.MockEmitter{}
ec := new(fakeEngineController)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, ec)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, nil, l1F, ec)
fi.AttachEmitter(emitter)

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

emitter := &testutils.MockEmitter{}
ec := new(fakeEngineController)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, ec)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, nil, l1F, ec)
fi.AttachEmitter(emitter)

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

emitter := &testutils.MockEmitter{}
ec := new(fakeEngineController)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, ec)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, nil, l1F, ec)
fi.AttachEmitter(emitter)

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

emitter := &testutils.MockEmitter{}
ec := new(fakeEngineController)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, ec)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, nil, l1F, ec)
fi.AttachEmitter(emitter)

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

// now say C0 and C1 were included in D and became the new safe head
Expand All @@ -504,3 +504,101 @@ func TestEngineQueue_Finalize(t *testing.T) {
emitter.AssertExpectations(t)
})
}

func TestFinalizerConfig(t *testing.T) {
t.Run("uses custom finality lookback", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelError)
l1F := &testutils.MockL1Source{}
ec := new(fakeEngineController)

customLookback := uint64(200)
finalizerCfg := &Config{
FinalityLookback: &customLookback,
}

fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, finalizerCfg, l1F, ec)

require.Equal(t, customLookback, fi.finalityLookback, "should use custom finality lookback")
require.Equal(t, int(customLookback), cap(fi.finalityData), "finalityData capacity should match custom lookback")
})

t.Run("uses custom finality delay", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelError)
l1F := &testutils.MockL1Source{}
ec := new(fakeEngineController)

customDelay := uint64(32)
finalizerCfg := &Config{
FinalityDelay: &customDelay,
}

fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, finalizerCfg, l1F, ec)

require.Equal(t, customDelay, fi.finalityDelay, "should use custom finality delay")
})

t.Run("uses defaults when config is nil", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelError)
l1F := &testutils.MockL1Source{}
ec := new(fakeEngineController)

fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, nil, l1F, ec)

require.Equal(t, uint64(defaultFinalityLookback), fi.finalityLookback, "should use default finality lookback when config is nil")
require.Equal(t, uint64(finalityDelay), fi.finalityDelay, "should use default finality delay when config is nil")
})

t.Run("uses defaults when config fields are nil", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelError)
l1F := &testutils.MockL1Source{}
ec := new(fakeEngineController)

// Passing empty config should behave same as nil
finalizerCfg := &Config{}

fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, finalizerCfg, l1F, ec)

require.Equal(t, uint64(defaultFinalityLookback), fi.finalityLookback, "should use default finality lookback when config fields are nil")
require.Equal(t, uint64(finalityDelay), fi.finalityDelay, "should use default finality delay when config fields are nil")
})

t.Run("uses alt-da lookback when configured", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelError)
l1F := &testutils.MockL1Source{}
ec := new(fakeEngineController)

cfg := &rollup.Config{
AltDAConfig: &rollup.AltDAConfig{
DAChallengeWindow: 90,
DAResolveWindow: 90,
},
}

fi := NewFinalizer(context.Background(), logger, cfg, nil, l1F, ec)

expectedLookback := uint64(181) // 90 + 90 + 1
require.Equal(t, expectedLookback, fi.finalityLookback, "should use alt-da calculated lookback")
})

t.Run("custom lookback overrides alt-da calculation", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelError)
l1F := &testutils.MockL1Source{}
ec := new(fakeEngineController)

cfg := &rollup.Config{
AltDAConfig: &rollup.AltDAConfig{
DAChallengeWindow: 90,
DAResolveWindow: 90,
},
}

customLookback := uint64(300)
finalizerCfg := &Config{
FinalityLookback: &customLookback,
}

fi := NewFinalizer(context.Background(), logger, cfg, finalizerCfg, l1F, ec)

require.Equal(t, customLookback, fi.finalityLookback, "custom lookback should override alt-da calculation")
})
}
17 changes: 16 additions & 1 deletion op-node/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/driver"
"github.com/ethereum-optimism/optimism/op-node/rollup/engine"
"github.com/ethereum-optimism/optimism/op-node/rollup/finality"
"github.com/ethereum-optimism/optimism/op-node/rollup/interop"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum-optimism/optimism/op-service/cliiface"
Expand Down Expand Up @@ -202,14 +203,28 @@ func NewConfigPersistence(ctx cliiface.Context) config.ConfigPersistence {
}

func NewDriverConfig(ctx cliiface.Context) *driver.Config {
return &driver.Config{
cfg := &driver.Config{
VerifierConfDepth: ctx.Uint64(flags.VerifierL1Confs.Name),
SequencerConfDepth: ctx.Uint64(flags.SequencerL1Confs.Name),
SequencerEnabled: ctx.Bool(flags.SequencerEnabledFlag.Name),
SequencerStopped: ctx.Bool(flags.SequencerStoppedFlag.Name),
SequencerMaxSafeLag: ctx.Uint64(flags.SequencerMaxSafeLagFlag.Name),
RecoverMode: ctx.Bool(flags.SequencerRecoverMode.Name),
}

// Populate finality config from flags. A finality config with null fields
// is handled the same way as a null finality config.
cfg.Finalizer = &finality.Config{}
if ctx.IsSet(flags.FinalityLookbackFlag.Name) {
lookback := ctx.Uint64(flags.FinalityLookbackFlag.Name)
cfg.Finalizer.FinalityLookback = &lookback
}
if ctx.IsSet(flags.FinalityDelayFlag.Name) {
delay := ctx.Uint64(flags.FinalityDelayFlag.Name)
cfg.Finalizer.FinalityDelay = &delay
}

return cfg
}

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