Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix finalizer_test.go #25

Merged
merged 11 commits into from
Jul 30, 2024
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
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