Skip to content

Commit

Permalink
op-batcher: Add threshold for L1 blob base fee
Browse files Browse the repository at this point in the history
  • Loading branch information
ironbeer committed Jul 23, 2024
1 parent 0db615d commit da79ba0
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 6 deletions.
6 changes: 6 additions & 0 deletions op-batcher/batcher/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ type CLIConfig struct {
// transactions sent to the transaction manager (0 == no limit).
MaxPendingTransactions uint64

// If L1 blob base fee exceeds this value, batch tx will not be sent.
// The unit is Wei, and if 0 is specified, there is no fee cap.
// The base fee may not be accurate as it is obtained from the GasPriceOracle on L2.
MaxL1BlobBaseFee uint64

// MaxL1TxSize is the maximum size of a batch tx submitted to L1.
// If using blobs, this setting is ignored and the max blob size is used.
MaxL1TxSize uint64
Expand Down Expand Up @@ -171,6 +176,7 @@ func NewConfig(ctx *cli.Context) *CLIConfig {
/* Optional Flags */
MaxPendingTransactions: ctx.Uint64(flags.MaxPendingTransactionsFlag.Name),
MaxChannelDuration: ctx.Uint64(flags.MaxChannelDurationFlag.Name),
MaxL1BlobBaseFee: ctx.Uint64(flags.MaxL1BlobBaseFeeFlag.Name),
MaxL1TxSize: ctx.Uint64(flags.MaxL1TxSizeBytesFlag.Name),
TargetNumFrames: ctx.Int(flags.TargetNumFramesFlag.Name),
ApproxComprRatio: ctx.Float64(flags.ApproxComprRatioFlag.Name),
Expand Down
24 changes: 24 additions & 0 deletions op-batcher/batcher/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/dial"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool"
Expand Down Expand Up @@ -487,6 +488,12 @@ func (l *BatchSubmitter) publishTxToL1(ctx context.Context, queue *txmgr.Queue[t
}
l.recordL1Tip(l1tip)

if l.Config.UseBlobs {
if err := l.checkL1BlobBaseFee(ctx); err != nil {
return err
}
}

// Collect next transaction data
txdata, err := l.state.TxData(l1tip.ID())

Expand Down Expand Up @@ -620,6 +627,23 @@ func (l *BatchSubmitter) calldataTxCandidate(data []byte) *txmgr.TxCandidate {
}
}

func (l *BatchSubmitter) checkL1BlobBaseFee(ctx context.Context) error {
if l.Config.MaxL1BlobBaseFee > 0 {
gpo, err := l.EndpointProvider.GasPriceOracle(ctx)
if err != nil {
return fmt.Errorf("could not get GasPriceOracle: %w", err)
}
blobBaseFee, err := gpo.BlobBaseFee(&bind.CallOpts{Context: ctx})
if err != nil {
return fmt.Errorf("could not get L1 Blob base fee from GasPriceOracle: %w", err)
}
if blobBaseFee.Cmp(new(big.Int).SetUint64(l.Config.MaxL1BlobBaseFee)) == 1 {
return fmt.Errorf("L1 Blob base fee exceeds threshold, blob_base_fee = %s", blobBaseFee)
}
}
return nil
}

func (l *BatchSubmitter) handleReceipt(r txmgr.TxReceipt[txRef]) {
// Record TX Status
if r.Err != nil {
Expand Down
49 changes: 43 additions & 6 deletions op-batcher/batcher/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package batcher
import (
"context"
"errors"
"math/big"
"testing"

"github.com/ethereum-optimism/optimism/op-batcher/metrics"
Expand All @@ -15,16 +16,19 @@ import (
)

type mockL2EndpointProvider struct {
ethClient *testutils.MockL2Client
ethClientErr error
rollupClient *testutils.MockRollupClient
rollupClientErr error
ethClient *testutils.MockL2Client
ethClientErr error
rollupClient *testutils.MockRollupClient
rollupClientErr error
gasPriceOracle *testutils.MockGasPriceOracle
gasPriceOracleErr error
}

func newEndpointProvider() *mockL2EndpointProvider {
return &mockL2EndpointProvider{
ethClient: new(testutils.MockL2Client),
rollupClient: new(testutils.MockRollupClient),
ethClient: new(testutils.MockL2Client),
rollupClient: new(testutils.MockRollupClient),
gasPriceOracle: new(testutils.MockGasPriceOracle),
}
}

Expand All @@ -36,6 +40,10 @@ func (p *mockL2EndpointProvider) RollupClient(context.Context) (dial.RollupClien
return p.rollupClient, p.rollupClientErr
}

func (p *mockL2EndpointProvider) GasPriceOracle(context.Context) (dial.GasPriceOracleInterface, error) {
return p.gasPriceOracle, p.gasPriceOracleErr
}

func (p *mockL2EndpointProvider) Close() {}

const genesisL1Origin = uint64(123)
Expand Down Expand Up @@ -117,3 +125,32 @@ func TestBatchSubmitter_SafeL1Origin_FailsToResolveRollupClient(t *testing.T) {
_, err := bs.safeL1Origin(context.Background())
require.Error(t, err)
}

func TestBatchSubmitter_CheckL1BlobBaseFee_NotSet(t *testing.T) {
bs, ep := setup(t)

ep.gasPriceOracle.ExpectBlobBaseFee(big.NewInt(1), nil)

err := bs.checkL1BlobBaseFee(context.Background())
require.NoError(t, err)
}

func TestBatchSubmitter_CheckL1BlobBaseFee_Succeeds(t *testing.T) {
bs, ep := setup(t)

bs.Config.MaxL1BlobBaseFee = 1
ep.gasPriceOracle.ExpectBlobBaseFee(big.NewInt(1), nil)

err := bs.checkL1BlobBaseFee(context.Background())
require.NoError(t, err)
}

func TestBatchSubmitter_CheckL1BlobBaseFee_Fails(t *testing.T) {
bs, ep := setup(t)

bs.Config.MaxL1BlobBaseFee = 1
ep.gasPriceOracle.ExpectBlobBaseFee(big.NewInt(2), nil)

err := bs.checkL1BlobBaseFee(context.Background())
require.ErrorContains(t, err, "L1 Blob base fee exceeds threshold")
}
2 changes: 2 additions & 0 deletions op-batcher/batcher/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type BatcherConfig struct {

WaitNodeSync bool
CheckRecentTxsDepth int
MaxL1BlobBaseFee uint64
}

// BatcherService represents a full batch-submitter instance and its resources,
Expand Down Expand Up @@ -100,6 +101,7 @@ func (bs *BatcherService) initFromCLIConfig(ctx context.Context, version string,
bs.NetworkTimeout = cfg.TxMgrConfig.NetworkTimeout
bs.CheckRecentTxsDepth = cfg.CheckRecentTxsDepth
bs.WaitNodeSync = cfg.WaitNodeSync
bs.MaxL1BlobBaseFee = cfg.MaxL1BlobBaseFee
if err := bs.initRPCClients(ctx, cfg); err != nil {
return err
}
Expand Down
9 changes: 9 additions & 0 deletions op-batcher/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ var (
Value: 0,
EnvVars: prefixEnvVars("MAX_CHANNEL_DURATION"),
}
MaxL1BlobBaseFeeFlag = &cli.Uint64Flag{
Name: "max-l1-blob-base-fee",
Usage: "If L1 blob base fee exceeds this value, batch tx will not be sent. " +
"The unit is Wei, and if 0 is specified, there is no fee cap. " +
"The base fee may not be accurate as it is obtained from the GasPriceOracle on L2.",
Value: 0,
EnvVars: prefixEnvVars("MAX_L1_BLOB_BASE_FEE"),
}
MaxL1TxSizeBytesFlag = &cli.Uint64Flag{
Name: "max-l1-tx-size-bytes",
Usage: "The maximum size of a batch tx submitted to L1. Ignored for blobs, where max blob size will be used.",
Expand Down Expand Up @@ -168,6 +176,7 @@ var optionalFlags = []cli.Flag{
PollIntervalFlag,
MaxPendingTransactionsFlag,
MaxChannelDurationFlag,
MaxL1BlobBaseFeeFlag,
MaxL1TxSizeBytesFlag,
TargetNumFramesFlag,
ApproxComprRatioFlag,
Expand Down
12 changes: 12 additions & 0 deletions op-service/dial/active_l2_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"fmt"
"time"

"github.com/ethereum-optimism/optimism/op-e2e/bindings"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/predeploys"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
Expand Down Expand Up @@ -115,6 +117,16 @@ func (p *ActiveL2EndpointProvider) EthClient(ctx context.Context) (EthClientInte
return p.currentEthClient, nil
}

func (p *ActiveL2EndpointProvider) GasPriceOracle(ctx context.Context) (GasPriceOracleInterface, error) {
if ec, err := p.EthClient(ctx); err != nil {
return nil, err
} else if t, ok := ec.(*ethclient.Client); !ok {
return nil, errors.New("not ethclient.Client")
} else {
return bindings.NewGasPriceOracle(predeploys.GasPriceOracleAddr, t)
}
}

func (p *ActiveL2EndpointProvider) Close() {
if p.currentEthClient != nil {
p.currentEthClient.Close()
Expand Down
13 changes: 13 additions & 0 deletions op-service/dial/gpo_interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dial

import (
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
)

// GasPriceOracleInterface is an interface for providing
// an GasPriceOracle pre-deployment contract
type GasPriceOracleInterface interface {
BlobBaseFee(opts *bind.CallOpts) (*big.Int, error)
}
8 changes: 8 additions & 0 deletions op-service/dial/static_l2_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package dial
import (
"context"

"github.com/ethereum-optimism/optimism/op-e2e/bindings"
"github.com/ethereum-optimism/optimism/op-service/predeploys"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
Expand All @@ -16,6 +18,8 @@ type L2EndpointProvider interface {
// Note: ctx should be a lifecycle context without an attached timeout as client selection may involve
// multiple network operations, specifically in the case of failover.
EthClient(ctx context.Context) (EthClientInterface, error)
// GasPriceOracle(ctx) returns the callable GasPriceOracle contract on the L2
GasPriceOracle(ctx context.Context) (GasPriceOracleInterface, error)
}

// StaticL2EndpointProvider is a L2EndpointProvider that always returns the same static RollupClient and eth client
Expand Down Expand Up @@ -44,6 +48,10 @@ func (p *StaticL2EndpointProvider) EthClient(context.Context) (EthClientInterfac
return p.ethClient, nil
}

func (p *StaticL2EndpointProvider) GasPriceOracle(context.Context) (GasPriceOracleInterface, error) {
return bindings.NewGasPriceOracle(predeploys.GasPriceOracleAddr, p.ethClient)
}

func (p *StaticL2EndpointProvider) Close() {
if p.ethClient != nil {
p.ethClient.Close()
Expand Down
21 changes: 21 additions & 0 deletions op-service/testutils/mock_gpo_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package testutils

import (
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/stretchr/testify/mock"
)

type MockGasPriceOracle struct {
mock.Mock
}

func (m *MockGasPriceOracle) BlobBaseFee(opts *bind.CallOpts) (*big.Int, error) {
out := m.Mock.Called(opts)
return out.Get(0).(*big.Int), out.Error(1)
}

func (m *MockGasPriceOracle) ExpectBlobBaseFee(blobBaseFee *big.Int, err error) {
m.Mock.On("BlobBaseFee", mock.Anything).Once().Return(blobBaseFee, err)
}

0 comments on commit da79ba0

Please sign in to comment.