Skip to content

Commit

Permalink
fix finalizer_test.go (ethereum-optimism#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
lesterli authored Jul 30, 2024
1 parent 20ce4dc commit 0d2a0da
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 13 deletions.
13 changes: 5 additions & 8 deletions op-node/rollup/finality/finalizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,19 +243,23 @@ func (fi *Finalizer) tryFinalize() {
finalizedL2 := fi.lastFinalizedL2 // may be zeroed if nothing was finalized since startup.
var finalizedDerivedFrom eth.BlockID
// go through the latest inclusion data, and find the last L2 block that was derived from a finalized L1 block
fi.log.Debug("try finalize", "finality_data", fi.finalityData, "last_finalized_l2", finalizedL2)
for _, fd := range fi.finalityData {
if fd.L2Block.Number > finalizedL2.Number && fd.L1Block.Number <= fi.finalizedL1.Number {
lastFinalizedBlock := fi.findLastFinalizedL2BlockWithConsecutiveQuorom(fd.L2Block.Number, finalizedL2.Number)

// set finalized block(s)
if lastFinalizedBlock != nil {
finalizedL2, finalizedDerivedFrom = fi.updateFinalized(*lastFinalizedBlock, lastFinalizedBlock.L1Origin)
finalizedL2 = *lastFinalizedBlock
finalizedDerivedFrom = fd.L1Block
fi.log.Debug("set finalized block", "finalized_l2", finalizedL2, "finalized_derived_from", finalizedDerivedFrom, "fd_l2_block", fd.L2Block)
}

// some blocks in the queried range is not BTC finalized, stop iterating to honor consecutive quorom
if lastFinalizedBlock == nil || lastFinalizedBlock.Number != fd.L2Block.Number {
break
}

// keep iterating, there may be later L2 blocks that can also be finalized
}
}
Expand Down Expand Up @@ -366,13 +370,6 @@ func (fi *Finalizer) findLastFinalizedL2BlockWithConsecutiveQuorom(
return nil
}

func (fi *Finalizer) updateFinalized(lastFinalizedBlock eth.L2BlockRef, fdL1Block eth.BlockID) (eth.L2BlockRef, eth.BlockID) {
finalizedL2 := lastFinalizedBlock
finalizedDerivedFrom := fdL1Block
fi.log.Debug("set finalized block", "l2_block", finalizedL2, "derived_from", finalizedDerivedFrom)
return finalizedL2, finalizedDerivedFrom
}

// onDerivedSafeBlock buffers the L1 block the safe head was fully derived from,
// to finalize it once the derived-from L1 block, or a later L1 block, finalizes.
func (fi *Finalizer) onDerivedSafeBlock(l2Safe eth.L2BlockRef, derivedFrom eth.L1BlockRef) {
Expand Down
92 changes: 87 additions & 5 deletions op-node/rollup/finality/finalizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"

"github.com/babylonchain/babylon-finality-gadget/sdk/cwclient"
"github.com/babylonchain/babylon-finality-gadget/testutil/mocks"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/rollup/engine"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/testutils"
"go.uber.org/mock/gomock"
)

func TestEngineQueue_Finalize(t *testing.T) {
Expand Down Expand Up @@ -91,6 +94,12 @@ func TestEngineQueue_Finalize(t *testing.T) {
BlockTime: 1,
SeqWindowSize: 2,
}
babylonCfg := &rollup.BabylonConfig{
ChainID: "chain-test",
ContractAddress: "bbn1eyfccmjm6732k7wp4p6gdjwhxjwsvje44j0hfx8nkgrm8fs7vqfsa3n3gc",
BitcoinRpc: "https://rpc.this-is-a-mock.com/btc",
}
cfg.BabylonConfig = babylonCfg
refA1 := eth.L2BlockRef{
Hash: testutils.RandomHash(rng),
Number: refA0.Number + 1,
Expand Down Expand Up @@ -190,8 +199,18 @@ func TestEngineQueue_Finalize(t *testing.T) {
l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil)
l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil)

l2F := &testutils.MockL2Client{}
defer l2F.AssertExpectations(t)
mockL2BlockRefByNumberWithTimes(l2F, 1, refA1, refB0, refB1, refC0, refC1)

emitter := &testutils.MockEmitter{}
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, emitter)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{BabylonConfig: babylonCfg}, l1F, l2F, emitter)

ctl := gomock.NewController(t)
defer ctl.Finish()
sdkClient := mocks.NewMockISdkClient(ctl)
fi.babylonFinalityClient = sdkClient
mockQueryBlockRangeBabylonFinalizedWithTimes(sdkClient, 1, refA1, refB0, refB1, refC0, refC1)

// now say C1 was included in D and became the new safe head
fi.OnEvent(engine.SafeDerivedEvent{Safe: refC1, DerivedFrom: refD})
Expand Down Expand Up @@ -224,8 +243,17 @@ func TestEngineQueue_Finalize(t *testing.T) {
l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) // to check finality signal
l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) // to check what was derived from (same in this case)

l2F := &testutils.MockL2Client{}
defer l2F.AssertExpectations(t)
mockL2BlockRefByNumberWithTimes(l2F, 2, refA1, refB0, refB1, refC0, refC1)

emitter := &testutils.MockEmitter{}
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, emitter)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{BabylonConfig: babylonCfg}, l1F, l2F, emitter)
ctl := gomock.NewController(t)
defer ctl.Finish()
sdkClient := mocks.NewMockISdkClient(ctl)
fi.babylonFinalityClient = sdkClient
mockQueryBlockRangeBabylonFinalizedWithTimes(sdkClient, 2, refA1, refB0, refB1, refC0, refC1)

// now say C1 was included in D and became the new safe head
fi.OnEvent(engine.SafeDerivedEvent{Safe: refC1, DerivedFrom: refD})
Expand Down Expand Up @@ -263,8 +291,15 @@ func TestEngineQueue_Finalize(t *testing.T) {
l1F := &testutils.MockL1Source{}
defer l1F.AssertExpectations(t)

l2F := &testutils.MockL2Client{}
defer l2F.AssertExpectations(t)

emitter := &testutils.MockEmitter{}
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, emitter)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{BabylonConfig: babylonCfg}, l1F, l2F, emitter)
ctl := gomock.NewController(t)
defer ctl.Finish()
sdkClient := mocks.NewMockISdkClient(ctl)
fi.babylonFinalityClient = sdkClient

fi.OnEvent(engine.SafeDerivedEvent{Safe: refC1, DerivedFrom: refD})
fi.OnEvent(derive.DeriverIdleEvent{Origin: refD})
Expand All @@ -283,6 +318,8 @@ func TestEngineQueue_Finalize(t *testing.T) {
emitter.ExpectOnce(engine.PromoteFinalizedEvent{Ref: refC1})
l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil)
l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil)
mockL2BlockRefByNumberWithTimes(l2F, 2, refA1, refB0, refB1, refC0, refC1)
mockQueryBlockRangeBabylonFinalizedWithTimes(sdkClient, 2, refA1, refB0, refB1, refC0, refC1)
fi.OnEvent(TryFinalizeEvent{})
emitter.AssertExpectations(t)
l1F.AssertExpectations(t)
Expand All @@ -296,6 +333,8 @@ func TestEngineQueue_Finalize(t *testing.T) {
emitter.ExpectOnce(engine.PromoteFinalizedEvent{Ref: refD0})
l1F.ExpectL1BlockRefByNumber(refE.Number, refE, nil)
l1F.ExpectL1BlockRefByNumber(refE.Number, refE, nil)
mockL2BlockRefByNumberWithTimes(l2F, 1, refD0)
mockQueryBlockRangeBabylonFinalizedWithTimes(sdkClient, 1, refD0)
fi.OnEvent(TryFinalizeEvent{})
emitter.AssertExpectations(t)
l1F.AssertExpectations(t)
Expand Down Expand Up @@ -334,6 +373,8 @@ func TestEngineQueue_Finalize(t *testing.T) {
emitter.ExpectOnce(engine.PromoteFinalizedEvent{Ref: refF1})
l1F.ExpectL1BlockRefByNumber(refH.Number, refH, nil)
l1F.ExpectL1BlockRefByNumber(refH.Number, refH, nil)
mockL2BlockRefByNumberWithTimes(l2F, 1, refD1, refE0, refE1, refF0, refF1)
mockQueryBlockRangeBabylonFinalizedWithTimes(sdkClient, 1, refD1, refE0, refE1, refF0, refF1)
fi.OnEvent(TryFinalizeEvent{})
emitter.AssertExpectations(t)
l1F.AssertExpectations(t)
Expand All @@ -348,8 +389,17 @@ func TestEngineQueue_Finalize(t *testing.T) {
l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) // check the signal
l1F.ExpectL1BlockRefByNumber(refC.Number, refC, nil) // check what we derived the L2 block from

l2F := &testutils.MockL2Client{}
defer l2F.AssertExpectations(t)
mockL2BlockRefByNumberWithTimes(l2F, 1, refA1, refB0, refB1)

emitter := &testutils.MockEmitter{}
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, emitter)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{BabylonConfig: babylonCfg}, l1F, l2F, emitter)
ctl := gomock.NewController(t)
defer ctl.Finish()
sdkClient := mocks.NewMockISdkClient(ctl)
fi.babylonFinalityClient = sdkClient
mockQueryBlockRangeBabylonFinalizedWithTimes(sdkClient, 1, refA1, refB0, refB1)

// now say B1 was included in C and became the new safe head
fi.OnEvent(engine.SafeDerivedEvent{Safe: refB1, DerivedFrom: refC})
Expand Down Expand Up @@ -384,8 +434,15 @@ func TestEngineQueue_Finalize(t *testing.T) {
l1F.ExpectL1BlockRefByNumber(refF.Number, refF, nil) // check signal
l1F.ExpectL1BlockRefByNumber(refE.Number, refE, nil) // post-reorg

l2F := &testutils.MockL2Client{}
defer l2F.AssertExpectations(t)

emitter := &testutils.MockEmitter{}
fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, emitter)
fi := NewFinalizer(context.Background(), logger, &rollup.Config{BabylonConfig: babylonCfg}, l1F, l2F, emitter)
ctl := gomock.NewController(t)
defer ctl.Finish()
sdkClient := mocks.NewMockISdkClient(ctl)
fi.babylonFinalityClient = sdkClient

// now say B1 was included in C and became the new safe head
fi.OnEvent(engine.SafeDerivedEvent{Safe: refB1, DerivedFrom: refC})
Expand Down Expand Up @@ -418,6 +475,10 @@ func TestEngineQueue_Finalize(t *testing.T) {
fi.OnEvent(engine.SafeDerivedEvent{Safe: refC0Alt, DerivedFrom: refDAlt})
fi.OnEvent(engine.SafeDerivedEvent{Safe: refC1Alt, DerivedFrom: refDAlt})

mockL2BlockRefByNumberWithTimes(l2F, 2, refA1, refB0, refB1, refC0Alt, refC1Alt)
mockQueryBlockRangeBabylonFinalizedWithTimes(sdkClient, 2, refA1, refB0, refB1)
mockQueryBlockRangeBabylonFinalizedWithTimes(sdkClient, 2, refC0Alt, refC1Alt)

// We get an early finality signal for F, of the chain that did not include refC0Alt and refC1Alt,
// as L1 block F does not build on DAlt.
// The finality signal was for a new chain, while derivation is on an old stale chain.
Expand Down Expand Up @@ -458,6 +519,9 @@ func TestEngineQueue_Finalize(t *testing.T) {
// and don't expect a finality attempt.
emitter.AssertExpectations(t)

mockL2BlockRefByNumberWithTimes(l2F, 1, refA1, refB0, refB1, refC0)
mockQueryBlockRangeBabylonFinalizedWithTimes(sdkClient, 1, refA1, refB0, refB1, refC0)

// if we reset the attempt, then we can finalize however.
fi.triedFinalizeAt = 0
emitter.ExpectOnce(TryFinalizeEvent{})
Expand All @@ -468,3 +532,21 @@ func TestEngineQueue_Finalize(t *testing.T) {
emitter.AssertExpectations(t)
})
}

func mockL2BlockRefByNumberWithTimes(l2F *testutils.MockL2Client, times int, refs ...eth.L2BlockRef) {
for _, ref := range refs {
l2F.ExpectL2BlockRefByNumberWithTimes(ref.Number, ref, times, nil)
}
}

func mockQueryBlockRangeBabylonFinalizedWithTimes(sdkClient *mocks.MockISdkClient, times int, refs ...eth.L2BlockRef) {
queryBlocks := make([]*cwclient.L2Block, len(refs))
for i, ref := range refs {
queryBlocks[i] = &cwclient.L2Block{
BlockHeight: ref.Number,
BlockHash: ref.Hash.String(),
BlockTimestamp: ref.Time,
}
}
sdkClient.EXPECT().QueryBlockRangeBabylonFinalized(queryBlocks).Return(&refs[len(refs)-1].Number, nil).Times(times)
}
4 changes: 4 additions & 0 deletions op-service/testutils/mock_l2.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ func (m *MockL2Client) ExpectL2BlockRefByNumber(num uint64, ref eth.L2BlockRef,
m.Mock.On("L2BlockRefByNumber", num).Once().Return(ref, &err)
}

func (m *MockL2Client) ExpectL2BlockRefByNumberWithTimes(num uint64, ref eth.L2BlockRef, times int, err error) {
m.Mock.On("L2BlockRefByNumber", num).Times(times).Return(ref, &err)
}

func (c *MockL2Client) L2BlockRefByHash(ctx context.Context, hash common.Hash) (eth.L2BlockRef, error) {
out := c.Mock.Called(hash)
return out.Get(0).(eth.L2BlockRef), out.Error(1)
Expand Down

0 comments on commit 0d2a0da

Please sign in to comment.