Skip to content

Commit

Permalink
optimize(op-node): make block produce stable when L1 latency unstable (
Browse files Browse the repository at this point in the history
…ethereum-optimism#87)

* optimize(op-node): make block produce stable when L1 latency unstable

* fix find origin logic

* fix origin_selector_test

* fix ci

* fix ci

---------

Co-authored-by: Owen <[email protected]>
  • Loading branch information
bnoieh and owen-reorg authored Dec 13, 2023
1 parent 3ebf4c9 commit 111ab38
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 6 deletions.
20 changes: 18 additions & 2 deletions op-node/rollup/driver/origin_selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"time"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/log"
Expand All @@ -16,6 +17,7 @@ import (
type L1Blocks interface {
derive.L1BlockRefByHashFetcher
derive.L1BlockRefByNumberFetcher
derive.L1ReceiptsFetcher
}

type L1OriginSelector struct {
Expand Down Expand Up @@ -48,14 +50,19 @@ func (los *L1OriginSelector) FindL1Origin(ctx context.Context, l2Head eth.L2Bloc
// If we are past the sequencer depth, we may want to advance the origin, but need to still
// check the time of the next origin.
pastSeqDrift := l2Head.Time+los.cfg.BlockTime > currentOrigin.Time+los.cfg.MaxSequencerDrift
// Limit the time to fetch next origin block by default
refCtx, refCancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer refCancel()
if pastSeqDrift {
log.Warn("Next L2 block time is past the sequencer drift + current origin time")
// Must fetch next L1 block as long as it may take, cause we are pastSeqDrift
refCtx = ctx
}

// Attempt to find the next L1 origin block, where the next origin is the immediate child of
// the current origin block.
// The L1 source can be shimmed to hide new L1 blocks and enforce a sequencer confirmation distance.
nextOrigin, err := los.l1.L1BlockRefByNumber(ctx, currentOrigin.Number+1)
nextOrigin, err := los.l1.L1BlockRefByNumber(refCtx, currentOrigin.Number+1)
if err != nil {
if pastSeqDrift {
return eth.L1BlockRef{}, fmt.Errorf("cannot build next L2 block past current L1 origin %s by more than sequencer time drift, and failed to find next L1 origin: %w", currentOrigin, err)
Expand All @@ -68,12 +75,21 @@ func (los *L1OriginSelector) FindL1Origin(ctx context.Context, l2Head eth.L2Bloc
return currentOrigin, nil
}

receiptsCached := true
receiptsCtx, receiptsCancel := context.WithTimeout(ctx, 10*time.Millisecond)
defer receiptsCancel()
_, _, err = los.l1.FetchReceipts(receiptsCtx, nextOrigin.Hash)
if err != nil {
receiptsCached = false
}

// If the next L2 block time is greater than the next origin block's time, we can choose to
// start building on top of the next origin. Sequencer implementation has some leeway here and
// could decide to continue to build on top of the previous origin until the Sequencer runs out
// of slack. For simplicity, we implement our Sequencer to always start building on the latest
// L1 block when we can.
if l2Head.Time+los.cfg.BlockTime >= nextOrigin.Time {
// If not pastSeqDrift and next origin receipts not cached, fallback to current origin.
if l2Head.Time+los.cfg.BlockTime >= nextOrigin.Time && (pastSeqDrift || receiptsCached) {
return nextOrigin, nil
}

Expand Down
4 changes: 4 additions & 0 deletions op-node/rollup/driver/origin_selector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func TestOriginSelectorAdvances(t *testing.T) {

l1.ExpectL1BlockRefByHash(a.Hash, a, nil)
l1.ExpectL1BlockRefByNumber(b.Number, b, nil)
l1.ExpectFetchReceipts(b.Hash, nil, nil, nil)

s := NewL1OriginSelector(log, cfg, l1)
next, err := s.FindL1Origin(context.Background(), l2Head)
Expand Down Expand Up @@ -87,6 +88,7 @@ func TestOriginSelectorRespectsOriginTiming(t *testing.T) {

l1.ExpectL1BlockRefByHash(a.Hash, a, nil)
l1.ExpectL1BlockRefByNumber(b.Number, b, nil)
l1.ExpectFetchReceipts(b.Hash, nil, nil, nil)

s := NewL1OriginSelector(log, cfg, l1)
next, err := s.FindL1Origin(context.Background(), l2Head)
Expand Down Expand Up @@ -208,6 +210,7 @@ func TestOriginSelectorSeqDriftRespectsNextOriginTime(t *testing.T) {

l1.ExpectL1BlockRefByHash(a.Hash, a, nil)
l1.ExpectL1BlockRefByNumber(b.Number, b, nil)
l1.ExpectFetchReceipts(b.Hash, nil, nil, nil)

s := NewL1OriginSelector(log, cfg, l1)
next, err := s.FindL1Origin(context.Background(), l2Head)
Expand Down Expand Up @@ -265,6 +268,7 @@ func TestOriginSelectorHandlesLateL1Blocks(t *testing.T) {
l1.ExpectL1BlockRefByHash(a.Hash, a, nil)
l1.ExpectL1BlockRefByHash(a.Hash, a, nil)
l1.ExpectL1BlockRefByNumber(b.Number, b, nil)
l1.ExpectFetchReceipts(b.Hash, nil, nil, nil)

l1Head := b
confDepthL1 := NewConfDepth(2, func() eth.L1BlockRef { return l1Head }, l1)
Expand Down
8 changes: 7 additions & 1 deletion op-node/rollup/driver/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,13 @@ func (s *Driver) eventLoop() {
case <-stepReqCh:
s.metrics.SetDerivationIdle(false)
s.log.Debug("Derivation process step", "onto_origin", s.derivation.Origin(), "attempts", stepAttempts)
err := s.derivation.Step(context.Background())
stepCtx := context.Background()
if s.driverConfig.SequencerEnabled && !s.driverConfig.SequencerStopped {
var cancelStep context.CancelFunc
stepCtx, cancelStep = context.WithTimeout(ctx, 3*time.Second)
defer cancelStep()
}
err := s.derivation.Step(stepCtx)
stepAttempts += 1 // count as attempt by default. We reset to 0 if we are making healthy progress.
if err == io.EOF {
s.log.Debug("Derivation process went idle", "progress", s.derivation.Origin(), "err", err)
Expand Down
6 changes: 3 additions & 3 deletions op-node/sources/l1_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ func L1ClientDefaultConfig(config *rollup.Config, trustRPC bool, kind RPCProvide
// Cache 3/2 worth of sequencing window of receipts and txs
span := int(config.SeqWindowSize) * 3 / 2
fullSpan := span
if span > 1000 { // sanity cap. If a large sequencing window is configured, do not make the cache too large
span = 1000
if span > 2000 { // sanity cap. If a large sequencing window is configured, do not make the cache too large
span = 2000
}
return &L1ClientConfig{
EthClientConfig: EthClientConfig{
Expand Down Expand Up @@ -132,6 +132,7 @@ func (s *L1Client) L1BlockRefByHash(ctx context.Context, hash common.Hash) (eth.
}

func (s *L1Client) GoOrUpdatePreFetchReceipts(ctx context.Context, l1Start uint64) error {
s.preFetchReceiptsStartBlockChan <- l1Start
s.preFetchReceiptsOnce.Do(func() {
s.log.Info("pre-fetching receipts start", "startBlock", l1Start)
go func() {
Expand Down Expand Up @@ -162,7 +163,6 @@ func (s *L1Client) GoOrUpdatePreFetchReceipts(ctx context.Context, l1Start uint6
}
}()
})
s.preFetchReceiptsStartBlockChan <- l1Start
return nil
}

Expand Down

0 comments on commit 111ab38

Please sign in to comment.