Skip to content

Commit 893011d

Browse files
authored
feat: replace l1 head with l1 finalized block as sequencer and deriva… (#319)
1 parent 690f783 commit 893011d

File tree

7 files changed

+82
-2
lines changed

7 files changed

+82
-2
lines changed

op-node/flags/flags.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,13 @@ var (
226226
Value: 15,
227227
Category: L1RPCCategory,
228228
}
229+
L1FinalizedConfDepth = &cli.BoolFlag{
230+
Name: "l1-finalized-confs",
231+
Usage: "Use L1 finalized block as the latest head for opBNB sequencer and derivation. When enabled, verifier.l1-confs and sequencer.l1-confs will be ignored.",
232+
EnvVars: prefixEnvVars("L1_FINALIZED_CONFS"),
233+
Value: false,
234+
Category: L1RPCCategory,
235+
}
229236
SequencerEnabledFlag = &cli.BoolFlag{
230237
Name: "sequencer.enabled",
231238
Usage: "Enable sequencing of new L2 blocks. A separate batch submitter has to be deployed to publish the data for verifiers.",
@@ -439,6 +446,7 @@ var optionalFlags = []cli.Flag{
439446
L1BlobRpcRateLimit,
440447
L1BlobRpcMaxBatchSize,
441448
VerifierL1Confs,
449+
L1FinalizedConfDepth,
442450
SequencerEnabledFlag,
443451
SequencerStoppedFlag,
444452
SequencerMaxSafeLagFlag,

op-node/rollup/driver/conf_depth.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55

66
"github.com/ethereum/go-ethereum"
7+
"github.com/ethereum/go-ethereum/log"
78

89
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
910
"github.com/ethereum-optimism/optimism/op-service/eth"
@@ -43,3 +44,45 @@ func (c *confDepth) L1BlockRefByNumber(ctx context.Context, num uint64) (eth.L1B
4344
}
4445

4546
var _ derive.L1Fetcher = (*confDepth)(nil)
47+
48+
// confDepth is an util that wraps the L1 input fetcher used in the pipeline,
49+
// and hides the part of the L1 chain with insufficient confirmations.
50+
//
51+
// At 0 depth the l1 head is completely ignored.
52+
type confDepthByL1Finalized struct {
53+
// everything fetched by hash is trusted already, so we implement those by embedding the fetcher
54+
derive.L1Fetcher
55+
l1Finalized func() eth.L1BlockRef
56+
depth uint64
57+
}
58+
59+
func NewConfDepthByL1Finalized(depth uint64, l1Finalized func() eth.L1BlockRef, fetcher derive.L1Fetcher) *confDepthByL1Finalized {
60+
return &confDepthByL1Finalized{L1Fetcher: fetcher, l1Finalized: l1Finalized, depth: depth}
61+
}
62+
63+
// L1BlockRefByNumber is used for L1 traversal and for finding a safe common point between the L2 engine and L1 chain.
64+
// Any block numbers that are within confirmation depth of the L1 head are mocked to be "not found",
65+
// effectively hiding the uncertain part of the L1 chain.
66+
func (c *confDepthByL1Finalized) L1BlockRefByNumber(ctx context.Context, num uint64) (eth.L1BlockRef, error) {
67+
// TODO: performance optimization: buffer the l1Unsafe, invalidate any reorged previous buffer content,
68+
// and instantly return the origin by number from the buffer if we can.
69+
70+
// Don't apply the conf depth if l1Head is empty (as it is during the startup case before the l1State is initialized).
71+
l1Finalized := c.l1Finalized()
72+
if l1Finalized == (eth.L1BlockRef{}) {
73+
// if l1Finalized is empty, wait for it to be set, temporarily return not found
74+
log.Warn("Conf depth is waiting for L1 finalized block to be set")
75+
return eth.L1BlockRef{}, ethereum.NotFound
76+
}
77+
if num == l1Finalized.Number {
78+
log.Info("Conf depth get L1 block number is equal to finalized block number", "num", num, "finalized", l1Finalized)
79+
return l1Finalized, nil
80+
}
81+
if num < l1Finalized.Number {
82+
return c.L1Fetcher.L1BlockRefByNumber(ctx, num)
83+
}
84+
log.Error("Conf depth get L1 block number is greater than finalized block number", "num", num, "finalized", l1Finalized)
85+
return eth.L1BlockRef{}, ethereum.NotFound
86+
}
87+
88+
var _ derive.L1Fetcher = (*confDepthByL1Finalized)(nil)

op-node/rollup/driver/config.go

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

33
type Config struct {
4+
// L1FinalizedConfDepth enables using L1 finalized block as the latest head for opBNB sequencer and derivation.
5+
// When enabled, VerifierConfDepth and SequencerConfDepth will be ignored.
6+
L1FinalizedConfDepth bool `json:"l1_finalized_conf_depth"`
7+
48
// VerifierConfDepth is the distance to keep from the L1 head when reading L1 data for L2 derivation.
9+
// Ignored when L1FinalizedConfDepth is enabled.
510
VerifierConfDepth uint64 `json:"verifier_conf_depth"`
611

712
// SequencerConfDepth is the distance to keep from the L1 head as origin when sequencing new L2 blocks.
813
// If this distance is too large, the sequencer may:
914
// - not adopt a L1 origin within the allowed time (rollup.Config.MaxSequencerDrift)
1015
// - not adopt a L1 origin that can be included on L1 within the allowed range (rollup.Config.SeqWindowSize)
1116
// and thus fail to produce a block with anything more than deposits.
17+
// Ignored when L1FinalizedConfDepth is enabled.
1218
SequencerConfDepth uint64 `json:"sequencer_conf_depth"`
1319

1420
// SequencerEnabled is true when the driver should sequence new blocks.

op-node/rollup/driver/driver.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,18 @@ func NewDriver(
154154
) *Driver {
155155
l1 = NewMeteredL1Fetcher(l1, metrics)
156156
l1State := NewL1State(log, metrics)
157-
sequencerConfDepth := NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1)
157+
158+
var sequencerConfDepth derive.L1Fetcher
159+
var verifConfDepth derive.L1Fetcher
160+
if driverCfg.L1FinalizedConfDepth {
161+
sequencerConfDepth = NewConfDepthByL1Finalized(driverCfg.SequencerConfDepth, l1State.L1Finalized, l1)
162+
verifConfDepth = NewConfDepthByL1Finalized(driverCfg.VerifierConfDepth, l1State.L1Finalized, l1)
163+
} else {
164+
sequencerConfDepth = NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1)
165+
verifConfDepth = NewConfDepth(driverCfg.VerifierConfDepth, l1State.L1Head, l1)
166+
}
167+
158168
findL1Origin := NewL1OriginSelector(log, cfg, sequencerConfDepth)
159-
verifConfDepth := NewConfDepth(driverCfg.VerifierConfDepth, l1State.L1Head, l1)
160169
engine := derive.NewEngineController(l2, log, metrics, cfg, syncCfg, driverCfg.SequencerCombinedEngine)
161170
clSync := clsync.NewCLSync(log, cfg, metrics, engine)
162171

op-node/rollup/driver/l1_state.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ func (s *L1State) HandleNewL1SafeBlock(safe eth.L1BlockRef) {
6060
func (s *L1State) HandleNewL1FinalizedBlock(finalized eth.L1BlockRef) {
6161
s.log.Info("New L1 finalized block", "l1_finalized", finalized)
6262
s.metrics.RecordL1Ref("l1_finalized", finalized)
63+
64+
if s.l1Finalized.Number > finalized.Number {
65+
s.log.Error("New L1 finalized block is less than the current finalized block", "l1_finalized", finalized, "current_l1_finalized", s.l1Finalized)
66+
}
67+
6368
s.l1Finalized = finalized
6469
}
6570

op-node/rollup/driver/state.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ func (s *Driver) eventLoop() {
307307
s.l1State.HandleNewL1HeadBlock(newL1Head)
308308
reqStep() // a new L1 head may mean we have the data to not get an EOF again.
309309
continue
310+
case newL1Finalized := <-s.l1FinalizedSig: // sequencerStep may depend on this when FindL1Origin
311+
s.l1State.HandleNewL1FinalizedBlock(newL1Finalized)
312+
reqStep() // a new L1 finalized may mean we have the data to not get an EOF again.
313+
continue
310314
default:
311315
// immediately do sequencerStep if time is ready
312316
if err := sequencerStep(); err != nil {
@@ -342,6 +346,10 @@ func (s *Driver) eventLoop() {
342346
s.l1State.HandleNewL1HeadBlock(newL1Head)
343347
reqStep() // a new L1 head may mean we have the data to not get an EOF again.
344348
continue
349+
case newL1Finalized := <-s.l1FinalizedSig: // sequencerStep may depend on this when FindL1Origin
350+
s.l1State.HandleNewL1FinalizedBlock(newL1Finalized)
351+
reqStep() // a new L1 finalized may mean we have the data to not get an EOF again.
352+
continue
345353
case respCh := <-s.stopSequencer:
346354
if s.driverConfig.SequencerStopped {
347355
respCh <- hashAndError{err: ErrSequencerAlreadyStopped}

op-node/service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ func NewConfigPersistence(ctx *cli.Context) node.ConfigPersistence {
204204

205205
func NewDriverConfig(ctx *cli.Context) *driver.Config {
206206
return &driver.Config{
207+
L1FinalizedConfDepth: ctx.Bool(flags.L1FinalizedConfDepth.Name),
207208
VerifierConfDepth: ctx.Uint64(flags.VerifierL1Confs.Name),
208209
SequencerConfDepth: ctx.Uint64(flags.SequencerL1Confs.Name),
209210
SequencerEnabled: ctx.Bool(flags.SequencerEnabledFlag.Name),

0 commit comments

Comments
 (0)