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
8 changes: 6 additions & 2 deletions op-node/rollup/sequencing/origin_selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ func (los *L1OriginSelector) OnEvent(ctx context.Context, ev event.Event) bool {
// FindL1Origin determines what the next L1 Origin should be.
// The L1 Origin is either the L2 Head's Origin, or the following L1 block
// if the next L2 block's time is greater than or equal to the L2 Head's Origin.
// The origin selection relies purely on block numbers and it is the caller's
// responsibility to detect and handle L1 reorgs.
func (los *L1OriginSelector) FindL1Origin(ctx context.Context, l2Head eth.L2BlockRef) (eth.L1BlockRef, error) {
currentOrigin, nextOrigin, err := los.CurrentAndNextOrigin(ctx, l2Head)
if err != nil {
Expand Down Expand Up @@ -170,8 +172,10 @@ func (los *L1OriginSelector) maybeSetNextOrigin(nextOrigin eth.L1BlockRef) {
los.mu.Lock()
defer los.mu.Unlock()

// Set the next origin if it is the immediate child of the current origin.
if nextOrigin.ParentHash == los.currentOrigin.Hash {
// Set the next origin if it is the subsequent block by number.
// On reorgs, this might not be the immediate child of the current origin
// since the hash is not checked.
if nextOrigin.Number == los.currentOrigin.Number+1 {
los.nextOrigin = nextOrigin
}
}
Expand Down
80 changes: 79 additions & 1 deletion op-node/rollup/sequencing/origin_selector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ func TestOriginSelectorFetchNextError(t *testing.T) {
// is no conf depth to stop the origin selection so block `b` should
// be the next L1 origin, and then block `c` is the subsequent L1 origin.
func TestOriginSelectorAdvances(t *testing.T) {

testOriginSelectorAdvances := func(t *testing.T, recoverMode bool) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Expand Down Expand Up @@ -340,6 +339,85 @@ func TestOriginSelectorFetchesNextOrigin(t *testing.T) {
require.Equal(t, b, next)
}

// TestOriginSelectorHandlesReorg ensures that the origin selector
// can handle the current origin being reorged out
//
// There are 3 blocks [a, b, c]. After advancing to b, a reorg is simulated
// where b is reorged and replaced by providing a `c` next that has a different parent hash.
// The origin should still provide c as the next origin so upstream services can detect the reorg.
func TestOriginSelectorHandlesReorg(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

log := testlog.Logger(t, log.LevelDebug)
cfg := &rollup.Config{
MaxSequencerDrift: 500,
BlockTime: 2,
}
l1 := &testutils.MockL1Source{}
defer l1.AssertExpectations(t)
a := eth.L1BlockRef{
Hash: common.Hash{'a'},
Number: 10,
Time: 20,
}
b := eth.L1BlockRef{
Hash: common.Hash{'b'},
Number: 11,
Time: 22,
ParentHash: a.Hash,
}
l2Head := eth.L2BlockRef{
L1Origin: a.ID(),
Time: 24,
}

// This is called as part of the background prefetch job
l1.ExpectL1BlockRefByNumber(b.Number, b, nil)

s := NewL1OriginSelector(ctx, log, cfg, l1)
s.currentOrigin = a

requireFindl1OriginEqual := func(l1ref eth.L1BlockRef) {
next, err := s.FindL1Origin(ctx, l2Head)
require.NoError(t, err)
require.Equal(t, l1ref, next)
}

requireFindl1OriginEqual(a)

// Selection is stable until the next origin is fetched
requireFindl1OriginEqual(a)

// Trigger the background fetch via a forkchoice update
handled := s.OnEvent(context.Background(), engine.ForkchoiceUpdateEvent{UnsafeL2Head: l2Head})
require.True(t, handled)

// The next origin should be `b` now.
requireFindl1OriginEqual(b)

// A reorg happens and `b` is replaced by a block with a different hash
c := eth.L1BlockRef{
Hash: common.Hash{'c'},
Number: 12,
Time: 24,
ParentHash: common.Hash{'b', '2'},
}
l1.ExpectL1BlockRefByNumber(c.Number, c, nil)
l2Head = eth.L2BlockRef{
L1Origin: b.ID(),
Time: 26,
}

// Trigger the background fetch via a forkchoice update
handled = s.OnEvent(context.Background(), engine.ForkchoiceUpdateEvent{UnsafeL2Head: l2Head})
require.True(t, handled)

// The next origin should be `c` now, otherwise an upstream service cannot detect the reorg
// and the origin will be stuck at `b`
requireFindl1OriginEqual(c)
}

// TestOriginSelectorRespectsOriginTiming ensures that the origin selector
// does not pick an origin that is ahead of the next L2 block time
//
Expand Down