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

feat: update tryFinalize() to check finality on each L2 block #23

Merged
merged 19 commits into from
Jul 26, 2024
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.21

require (
github.com/andybalholm/brotli v1.1.0
github.com/babylonchain/babylon-finality-gadget v0.0.0-20240708032647-4fdeba25732d
github.com/babylonchain/babylon-finality-gadget v0.1.3-alpha.0.20240725201932-fe0411e65930
github.com/btcsuite/btcd v0.24.2
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
github.com/cockroachdb/pebble v1.1.0
Expand Down Expand Up @@ -42,6 +42,7 @@ require (
github.com/prometheus/client_golang v1.19.1
github.com/stretchr/testify v1.9.0
github.com/urfave/cli/v2 v2.27.1
go.uber.org/mock v0.4.0
golang.org/x/crypto v0.24.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/sync v0.7.0
Expand Down Expand Up @@ -350,7 +351,6 @@ require (
go.uber.org/automaxprocs v1.5.2 // indirect
go.uber.org/dig v1.17.1 // indirect
go.uber.org/fx v1.21.1 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/mod v0.17.0 // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,8 @@ github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX
github.com/aws/aws-sdk-go v1.44.312 h1:llrElfzeqG/YOLFFKjg1xNpZCFJ2xraIi3PqSuP+95k=
github.com/aws/aws-sdk-go v1.44.312/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/babylonchain/babylon-finality-gadget v0.0.0-20240708032647-4fdeba25732d h1:iGEbm7HfamD3lDs1q2Ui3TV6wWlhWtVhD7ErBOGAvCM=
github.com/babylonchain/babylon-finality-gadget v0.0.0-20240708032647-4fdeba25732d/go.mod h1:tsFvb/ZnQpBNtXmiCHoClBu/QRPLuCSr2lncexlhQSQ=
github.com/babylonchain/babylon-finality-gadget v0.1.3-alpha.0.20240725201932-fe0411e65930 h1:AEhnrx3+H30mTwGDYSwNHP/DbbH1+QgHU5KC8TW6+oY=
github.com/babylonchain/babylon-finality-gadget v0.1.3-alpha.0.20240725201932-fe0411e65930/go.mod h1:RG4+gysH91XhqxvmJf2uDtLtFHNZNW3Jg8BkjKdnGvA=
github.com/babylonchain/babylon-private v0.8.6-0.20240705135310-e91ff7f60ead h1:LyyrFtdSbx0a5ZLHA/qMNMjAxZltfnNJgj5+uEgqZdI=
github.com/babylonchain/babylon-private v0.8.6-0.20240705135310-e91ff7f60ead/go.mod h1:Tdi+29Y+DzCaaz0V0sBRXF1tXXLnH6JHJsOG8uktWLU=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
Expand Down Expand Up @@ -867,8 +867,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
Expand Down
8 changes: 4 additions & 4 deletions op-chain-ops/genesis/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,10 @@ type DeployConfig struct {
// UseInterop is a flag that indicates if the system is using interop
UseInterop bool `json:"useInterop,omitempty"`

// BabylonFinalityGadgetChainType is the chain type for the Babylon finality gadget.
BabylonFinalityGadgetChainType int `json:"babylonFinalityGadgetChainType"`
// BabylonFinalityGadgetChainID is the Chain ID of the Babylon chain for the Babylon finality gadget
BabylonFinalityGadgetChainID string `json:"babylonFinalityGadgetChainID"`

// BabylonFinalityGadgetContractAddress is the contract address for the Babylon finality gadget on BabylonChain.
// BabylonFinalityGadgetContractAddress is the contract address for the Babylon finality gadget on Babylon chain.
BabylonFinalityGadgetContractAddress string `json:"babylonFinalityGadgetContractAddress"`

// BabylonFinalityGadgetBitcoinRpc is the Bitcoin RPC URL for the Babylon finality gadget.
Expand Down Expand Up @@ -667,7 +667,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Block, l2GenesisBlockHas
InteropTime: d.InteropTime(l1StartBlock.Time()),
PlasmaConfig: plasma,
BabylonConfig: &rollup.BabylonConfig{
ChainType: d.BabylonFinalityGadgetChainType,
ChainID: d.BabylonFinalityGadgetChainID,
ContractAddress: d.BabylonFinalityGadgetContractAddress,
BitcoinRpc: d.BabylonFinalityGadgetBitcoinRpc,
},
Expand Down
4 changes: 2 additions & 2 deletions op-e2e/actions/l2_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ func NewL2Verifier(t Testing, log log.Logger, l1 derive.L1Fetcher, blobsSrc deri

var finalizer driver.Finalizer
if cfg.PlasmaEnabled() {
finalizer = finality.NewPlasmaFinalizer(ctx, log, cfg, l1, synchronousEvents, plasmaSrc)
finalizer = finality.NewPlasmaFinalizer(ctx, log, cfg, l1, eng, synchronousEvents, plasmaSrc)
} else {
finalizer = finality.NewFinalizer(ctx, log, cfg, l1, synchronousEvents)
finalizer = finality.NewFinalizer(ctx, log, cfg, l1, eng, synchronousEvents)
}

attributesHandler := attributes.NewAttributesHandler(log, cfg, ctx, eng, synchronousEvents)
Expand Down
2 changes: 1 addition & 1 deletion op-e2e/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
InteropTime: cfg.DeployConfig.InteropTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)),
ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy,
BabylonConfig: &rollup.BabylonConfig{
ChainType: cfg.DeployConfig.BabylonFinalityGadgetChainType,
ChainID: cfg.DeployConfig.BabylonFinalityGadgetChainID,
ContractAddress: cfg.DeployConfig.BabylonFinalityGadgetContractAddress,
BitcoinRpc: cfg.DeployConfig.BabylonFinalityGadgetBitcoinRpc,
},
Expand Down
4 changes: 2 additions & 2 deletions op-node/rollup/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,9 @@ func NewDriver(

var finalizer Finalizer
if cfg.PlasmaEnabled() {
finalizer = finality.NewPlasmaFinalizer(driverCtx, log, cfg, l1, synchronousEvents, plasma)
finalizer = finality.NewPlasmaFinalizer(driverCtx, log, cfg, l1, l2, synchronousEvents, plasma)
} else {
finalizer = finality.NewFinalizer(driverCtx, log, cfg, l1, synchronousEvents)
finalizer = finality.NewFinalizer(driverCtx, log, cfg, l1, l2, synchronousEvents)
}

attributesHandler := attributes.NewAttributesHandler(log, cfg, driverCtx, l2, synchronousEvents)
Expand Down
132 changes: 99 additions & 33 deletions op-node/rollup/finality/finalizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup/engine"
"github.com/ethereum-optimism/optimism/op-service/eth"

"github.com/babylonchain/babylon-finality-gadget/btcclient"
"github.com/babylonchain/babylon-finality-gadget/sdk"
"github.com/babylonchain/babylon-finality-gadget/sdk/btcclient"
sdkclient "github.com/babylonchain/babylon-finality-gadget/sdk/client"
sdkcfg "github.com/babylonchain/babylon-finality-gadget/sdk/config"
"github.com/babylonchain/babylon-finality-gadget/sdk/cwclient"
)

// defaultFinalityLookback defines the amount of L1<>L2 relations to track for finalization purposes, one per L1 block.
Expand Down Expand Up @@ -69,8 +71,12 @@ type FinalizerL1Interface interface {
L1BlockRefByNumber(context.Context, uint64) (eth.L1BlockRef, error)
}

type FinalizerL2Interface interface {
L2BlockRefByNumber(context.Context, uint64) (eth.L2BlockRef, error)
}

type BabylonFinalityClient interface {
QueryIsBlockBabylonFinalized(queryParams *sdk.L2Block) (bool, error)
QueryBlockRangeBabylonFinalized(queryBlocks []*cwclient.L2Block) (*uint64, error)
}

type Finalizer struct {
Expand Down Expand Up @@ -100,28 +106,30 @@ type Finalizer struct {

l1Fetcher FinalizerL1Interface

l2Fetcher FinalizerL2Interface

// babylonFinalityClient is the Babylon DA SDK client
babylonFinalityClient BabylonFinalityClient
}

func NewFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config, l1Fetcher FinalizerL1Interface, emitter rollup.EventEmitter) *Finalizer {
func NewFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config, l1Fetcher FinalizerL1Interface, l2Fetcher FinalizerL2Interface, emitter rollup.EventEmitter) *Finalizer {
lookback := calcFinalityLookback(cfg)

// Initialize the Babylon Finality client
btcConfig := btcclient.DefaultBTCConfig()
btcConfig.RPCHost = cfg.BabylonConfig.BitcoinRpc
config := &sdk.Config{
ChainType: cfg.BabylonConfig.ChainType,
config := &sdkcfg.Config{
ChainID: cfg.BabylonConfig.ChainID,
ContractAddr: cfg.BabylonConfig.ContractAddress,
BTCConfig: btcConfig,
}
log.Debug(
"creating Babylon Finality client",
"chain_type", config.ChainType,
"chain_id", config.ChainID,
"contract_address", config.ContractAddr,
"btc_rpc_host", config.BTCConfig.RPCHost,
)
babylonFinalityClient, err := sdk.NewClient(config)
babylonFinalityClient, err := sdkclient.NewClient(config)
if err != nil {
emitter.Emit(rollup.CriticalErrorEvent{Err: fmt.Errorf("failed to initialize Babylon Finality client: %w", err)})
return nil
Expand All @@ -135,6 +143,7 @@ func NewFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config, l1Fet
finalityData: make([]FinalityData, 0, lookback),
finalityLookback: lookback,
l1Fetcher: l1Fetcher,
l2Fetcher: l2Fetcher,
emitter: emitter,
babylonFinalityClient: babylonFinalityClient,
}
Expand Down Expand Up @@ -236,33 +245,16 @@ func (fi *Finalizer) tryFinalize() {
// go through the latest inclusion data, and find the last L2 block that was derived from a finalized L1 block
for _, fd := range fi.finalityData {
if fd.L2Block.Number > finalizedL2.Number && fd.L1Block.Number <= fi.finalizedL1.Number {
// check if fd.L2Block.Number is finalized on Babylon
queryParams := &sdk.L2Block{
BlockHeight: fd.L2Block.Number,
BlockHash: fd.L2Block.Hash.String(),
BlockTimestamp: fd.L2Block.Time,
}
fi.log.Debug(
"babylon gadget query params",
"block_height", queryParams.BlockHeight,
"block_hash", queryParams.BlockHash,
"block_timestamp", queryParams.BlockTimestamp,
)
babylonFinalized, err := fi.babylonFinalityClient.QueryIsBlockBabylonFinalized(queryParams)
fi.log.Debug("babylon gadget query result", "babylon_finalized", babylonFinalized)

// If the error encountered is of type NoFpHasVotingPowerError, it should be ignored;
// for any other error types, emit a critical error event.
if err != nil && !errors.Is(err, sdk.ErrNoFpHasVotingPower) {
fi.emitter.Emit(rollup.CriticalErrorEvent{Err: fmt.Errorf("failed to check if block %d is finalized on Babylon: %w", fd.L2Block.Number, err)})
return
lastFinalizedBlock := fi.findLastFinalizedL2BlockWithConsecutiveQuorom(fd.L2Block.Number, finalizedL2.Number)

// set finalized block(s)
if lastFinalizedBlock != (eth.L2BlockRef{}) {
finalizedL2, finalizedDerivedFrom = fi.updateFinalized(lastFinalizedBlock, lastFinalizedBlock.L1Origin)
}

// set finalized status
if babylonFinalized {
fi.log.Debug("set finalized status", "l2_block", fd.L2Block)
finalizedL2 = fd.L2Block
finalizedDerivedFrom = fd.L1Block
// some blocks in the queried range is not BTC finalized, stop iterating to honor consecutive quorom
if lastFinalizedBlock.Number != fd.L2Block.Number {
break
}
// keep iterating, there may be later L2 blocks that can also be finalized
bap2pecs marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down Expand Up @@ -300,6 +292,80 @@ func (fi *Finalizer) tryFinalize() {
}
}

/*
* findLastFinalizedL2BlockWithConsecutiveQuorom tries to find the finalized L2 block.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tries to find the last finalized L2 block with consecutive quorom

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in c89dd55

*
* If there are any L2 blocks that are not finalized, then we can't finalize the later ones.
* This is because we need to finalize the L2 blocks in order to guarantee consecutive quorom.
*
* It queries the Babylon gadget to check if the L2 blocks are finalized.
* If the error encountered is of type NoFpHasVotingPowerError, it should be ignored;
* for any other error types, emit an critical error event.
*/
func (fi *Finalizer) findLastFinalizedL2BlockWithConsecutiveQuorom(
fdL2BlockNumber uint64,
finalizedL2Number uint64,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's add some comments to explain the return value and those arguments

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in c89dd55

) eth.L2BlockRef {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not return a pointer?

i think using a pointer type will be cleaner so the check above

if lastFinalizedBlock.Number != fd.L2Block.Number {

will be changed to

if lastFinalizedBlock == nil || lastFinalizedBlock.Number != fd.L2Block.Number {

so it's more explicit

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in c89dd55

blockCount := int(fdL2BlockNumber - finalizedL2Number)
l2Blocks := make(map[uint64]eth.L2BlockRef)
queryBlocks := make([]*cwclient.L2Block, blockCount)

for i := 0; i < blockCount; i++ {
blockNumber := uint64(i) + finalizedL2Number + uint64(1)
l2Block, err := fi.l2Fetcher.L2BlockRefByNumber(fi.ctx, blockNumber)
if err != nil {
fi.emitter.Emit(rollup.CriticalErrorEvent{Err: fmt.Errorf(
"failed to check if block %d to %d is finalized on Babylon, could not fetch block %d: %w",
finalizedL2Number+1,
fdL2BlockNumber,
blockNumber,
err,
)})
return eth.L2BlockRef{}
}
l2Blocks[blockNumber] = l2Block

queryBlocks[i] = &cwclient.L2Block{
BlockHeight: l2Block.Number,
BlockHash: l2Block.Hash.String(),
BlockTimestamp: l2Block.Time,
}
fi.log.Debug(
"added block to babylon gadget's query params",
"block_height", queryBlocks[i].BlockHeight,
"block_hash", queryBlocks[i].BlockHash,
"block_timestamp", queryBlocks[i].BlockTimestamp,
)
}

lastFinalizedBlockNumber, err := fi.babylonFinalityClient.QueryBlockRangeBabylonFinalized(queryBlocks)

// TODO: we shouldn't skip on no voting power.
// https://github.com/babylonchain/babylon-finality-gadget/issues/59
if err != nil && !errors.Is(err, sdkclient.ErrNoFpHasVotingPower) {
bap2pecs marked this conversation as resolved.
Show resolved Hide resolved
if lastFinalizedBlockNumber != nil {
fi.log.Warn("Received error but also had the finalized block", "error", err, "blockNumber", *lastFinalizedBlockNumber)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the logic here is not clean for the same reason that we have too many unnecessary nesting and condition checks

it can be simplified to be:

  • if there is error, emit
  • if last finalized block is not nil, return the block
  • else return nil

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in c89dd55

return l2Blocks[*lastFinalizedBlockNumber]
}
fi.emitter.Emit(rollup.CriticalErrorEvent{Err: fmt.Errorf(
"failed to check if block %d to %d is finalized on Babylon: %w",
finalizedL2Number+1,
fdL2BlockNumber,
err,
)})
return eth.L2BlockRef{}
}

return l2Blocks[*lastFinalizedBlockNumber]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if lastFinalizedBlockNumber is nil?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in c89dd55

}

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
Loading