From cfe6fd2f45b5f6ddb371e2e2d2647f49163a9ce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 26 Nov 2025 19:03:53 +0100 Subject: [PATCH 01/10] fix(sender): log msg on error --- common/version/version.go | 2 +- rollup/internal/controller/sender/estimategas.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common/version/version.go b/common/version/version.go index 6418ac17b7..d77ad10a66 100644 --- a/common/version/version.go +++ b/common/version/version.go @@ -5,7 +5,7 @@ import ( "runtime/debug" ) -var tag = "v4.7.5" +var tag = "v4.7.6" var commit = func() string { if info, ok := debug.ReadBuildInfo(); ok { diff --git a/rollup/internal/controller/sender/estimategas.go b/rollup/internal/controller/sender/estimategas.go index 3d29838655..d9c7ed802e 100644 --- a/rollup/internal/controller/sender/estimategas.go +++ b/rollup/internal/controller/sender/estimategas.go @@ -2,6 +2,7 @@ package sender import ( "errors" + "fmt" "math/big" "github.com/scroll-tech/go-ethereum" @@ -118,7 +119,7 @@ func (s *Sender) estimateGasLimit(to *common.Address, data []byte, sidecar *type gasLimitWithoutAccessList, err := s.client.EstimateGas(s.ctx, msg) if err != nil { - log.Error("estimateGasLimit EstimateGas failure without access list", "error", err) + log.Error("estimateGasLimit EstimateGas failure without access list", "error", err, "msg", fmt.Sprintf("%+v", msg)) return 0, nil, err } From 2880bd500bd4b6cca40eb4c2718cf7a968f9963f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 26 Nov 2025 20:43:02 +0100 Subject: [PATCH 02/10] add more logs --- rollup/internal/controller/sender/estimategas.go | 1 + rollup/internal/controller/sender/sender.go | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/rollup/internal/controller/sender/estimategas.go b/rollup/internal/controller/sender/estimategas.go index d9c7ed802e..36a9d93656 100644 --- a/rollup/internal/controller/sender/estimategas.go +++ b/rollup/internal/controller/sender/estimategas.go @@ -82,6 +82,7 @@ func (s *Sender) estimateBlobGas(to *common.Address, data []byte, sidecar *types gasFeeCap := getGasFeeCap(new(big.Int).SetUint64(baseFee), gasTipCap) blobGasFeeCap := getBlobGasFeeCap(new(big.Int).SetUint64(blobBaseFee)) + log.Warn("estimateBlobGas", "blobBaseFee", blobBaseFee, "blobGasFeeCap", blobGasFeeCap.String()) gasLimit, accessList, err := s.estimateGasLimit(to, data, sidecar, nil, gasTipCap, gasFeeCap, blobGasFeeCap) if err != nil { log.Error("estimateBlobGas estimateGasLimit failure", diff --git a/rollup/internal/controller/sender/sender.go b/rollup/internal/controller/sender/sender.go index cf23436a31..4b3a7a2e96 100644 --- a/rollup/internal/controller/sender/sender.go +++ b/rollup/internal/controller/sender/sender.go @@ -834,14 +834,18 @@ func (s *Sender) getBlockNumberAndTimestampAndBaseFeeAndBlobFee(ctx context.Cont return 0, 0, 0, 0, fmt.Errorf("failed to get header by number, err: %w", err) } + log.Warn("getBlockNumberAndTimestampAndBaseFeeAndBlobFee", "number", header.Number.Uint64()) + var baseFee uint64 if header.BaseFee != nil { baseFee = header.BaseFee.Uint64() + log.Warn("getBlockNumberAndTimestampAndBaseFeeAndBlobFee", "baseFee", header.BaseFee.String(), "baseFeeUint64", baseFee) } var blobBaseFee uint64 if excess := header.ExcessBlobGas; excess != nil { blobBaseFee = misc.CalcBlobFee(*excess).Uint64() + log.Warn("getBlockNumberAndTimestampAndBaseFeeAndBlobFee", "blobBaseFee", misc.CalcBlobFee(*excess).String(), "blobBaseFeeUint64", blobBaseFee) } // header.Number.Uint64() returns the pendingBlockNumber, so we minus 1 to get the latestBlockNumber. return header.Number.Uint64() - 1, header.Time, baseFee, blobBaseFee, nil From 93604d944e0ac0347959c7604d193190c86d401d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 26 Nov 2025 21:12:04 +0100 Subject: [PATCH 03/10] query from L1 RPC --- rollup/internal/controller/sender/sender.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/rollup/internal/controller/sender/sender.go b/rollup/internal/controller/sender/sender.go index 4b3a7a2e96..d4ae235972 100644 --- a/rollup/internal/controller/sender/sender.go +++ b/rollup/internal/controller/sender/sender.go @@ -10,10 +10,10 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/holiman/uint256" "github.com/prometheus/client_golang/prometheus" "github.com/scroll-tech/go-ethereum/common" - "github.com/scroll-tech/go-ethereum/consensus/misc" gethTypes "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/crypto/kzg4844" "github.com/scroll-tech/go-ethereum/ethclient" @@ -67,7 +67,8 @@ type FeeData struct { // Sender Transaction sender to send transaction to l1/l2 type Sender struct { config *config.SenderConfig - gethClient *gethclient.Client + rpcClient *rpc.Client // Raw RPC Client + gethClient *gethclient.Client // Client to use for CreateAccessList client *ethclient.Client // The client to retrieve on chain data (read-only) writeClients []*ethclient.Client // The clients to send transactions to (write operations) transactionSigner *TransactionSigner @@ -141,6 +142,7 @@ func NewSender(ctx context.Context, config *config.SenderConfig, signerConfig *c sender := &Sender{ ctx: ctx, config: config, + rpcClient: rpcClient, gethClient: gethclient.New(rpcClient), client: client, writeClients: writeClients, @@ -842,11 +844,17 @@ func (s *Sender) getBlockNumberAndTimestampAndBaseFeeAndBlobFee(ctx context.Cont log.Warn("getBlockNumberAndTimestampAndBaseFeeAndBlobFee", "baseFee", header.BaseFee.String(), "baseFeeUint64", baseFee) } - var blobBaseFee uint64 - if excess := header.ExcessBlobGas; excess != nil { - blobBaseFee = misc.CalcBlobFee(*excess).Uint64() - log.Warn("getBlockNumberAndTimestampAndBaseFeeAndBlobFee", "blobBaseFee", misc.CalcBlobFee(*excess).String(), "blobBaseFeeUint64", blobBaseFee) + // Leave it up to the L1 node to return the correct blob base fee. + // Previously we would compute it locally using `CalcBlobFee`, but + // that needs to be in sync with the L1 node's configuration. + var hex hexutil.Big + if err := s.rpcClient.CallContext(ctx, &hex, "eth_blobBaseFee"); err != nil { + return 0, 0, 0, 0, fmt.Errorf("failed to call eth_blobBaseFee, err: %w", err) } + blobBaseFee := hex.ToInt().Uint64() + + log.Warn("getBlockNumberAndTimestampAndBaseFeeAndBlobFee", "blobBaseFeeUint64", blobBaseFee) + // header.Number.Uint64() returns the pendingBlockNumber, so we minus 1 to get the latestBlockNumber. return header.Number.Uint64() - 1, header.Time, baseFee, blobBaseFee, nil } From 8e9057adc65d76ab764246d1883d625368f3c8b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 26 Nov 2025 21:33:38 +0100 Subject: [PATCH 04/10] go mod tidy --- rollup/go.mod | 3 ++- rollup/go.sum | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/rollup/go.mod b/rollup/go.mod index 6229f23a3f..d14eaa9cf3 100644 --- a/rollup/go.mod +++ b/rollup/go.mod @@ -10,6 +10,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.80.0 github.com/consensys/gnark-crypto v0.16.0 github.com/crate-crypto/go-kzg-4844 v1.1.0 + github.com/ethereum/go-ethereum v1.10.26 github.com/gin-gonic/gin v1.9.1 github.com/go-resty/resty/v2 v2.7.0 github.com/holiman/uint256 v1.3.2 @@ -51,7 +52,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect + github.com/deckarep/golang-set v1.8.0 // indirect github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect github.com/fjl/memsize v0.0.2 // indirect diff --git a/rollup/go.sum b/rollup/go.sum index b9b5c43c05..f23e89ef86 100644 --- a/rollup/go.sum +++ b/rollup/go.sum @@ -88,13 +88,15 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0= -github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s= github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs= +github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= +github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= From c178e2c112d31f0ef61177f1c17813d02703a7e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 26 Nov 2025 21:34:44 +0100 Subject: [PATCH 05/10] clean up --- rollup/internal/controller/sender/estimategas.go | 1 - rollup/internal/controller/sender/sender.go | 5 ----- 2 files changed, 6 deletions(-) diff --git a/rollup/internal/controller/sender/estimategas.go b/rollup/internal/controller/sender/estimategas.go index 36a9d93656..d9c7ed802e 100644 --- a/rollup/internal/controller/sender/estimategas.go +++ b/rollup/internal/controller/sender/estimategas.go @@ -82,7 +82,6 @@ func (s *Sender) estimateBlobGas(to *common.Address, data []byte, sidecar *types gasFeeCap := getGasFeeCap(new(big.Int).SetUint64(baseFee), gasTipCap) blobGasFeeCap := getBlobGasFeeCap(new(big.Int).SetUint64(blobBaseFee)) - log.Warn("estimateBlobGas", "blobBaseFee", blobBaseFee, "blobGasFeeCap", blobGasFeeCap.String()) gasLimit, accessList, err := s.estimateGasLimit(to, data, sidecar, nil, gasTipCap, gasFeeCap, blobGasFeeCap) if err != nil { log.Error("estimateBlobGas estimateGasLimit failure", diff --git a/rollup/internal/controller/sender/sender.go b/rollup/internal/controller/sender/sender.go index d4ae235972..8c754cee6b 100644 --- a/rollup/internal/controller/sender/sender.go +++ b/rollup/internal/controller/sender/sender.go @@ -836,12 +836,9 @@ func (s *Sender) getBlockNumberAndTimestampAndBaseFeeAndBlobFee(ctx context.Cont return 0, 0, 0, 0, fmt.Errorf("failed to get header by number, err: %w", err) } - log.Warn("getBlockNumberAndTimestampAndBaseFeeAndBlobFee", "number", header.Number.Uint64()) - var baseFee uint64 if header.BaseFee != nil { baseFee = header.BaseFee.Uint64() - log.Warn("getBlockNumberAndTimestampAndBaseFeeAndBlobFee", "baseFee", header.BaseFee.String(), "baseFeeUint64", baseFee) } // Leave it up to the L1 node to return the correct blob base fee. @@ -853,8 +850,6 @@ func (s *Sender) getBlockNumberAndTimestampAndBaseFeeAndBlobFee(ctx context.Cont } blobBaseFee := hex.ToInt().Uint64() - log.Warn("getBlockNumberAndTimestampAndBaseFeeAndBlobFee", "blobBaseFeeUint64", blobBaseFee) - // header.Number.Uint64() returns the pendingBlockNumber, so we minus 1 to get the latestBlockNumber. return header.Number.Uint64() - 1, header.Time, baseFee, blobBaseFee, nil } From 692fd0f282e9ae8b5c0ec6b6ed496a2078dea065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 26 Nov 2025 21:52:24 +0100 Subject: [PATCH 06/10] fix gas-oracle --- common/testcontainers/testcontainers.go | 16 ++++++++----- common/testcontainers/testcontainers_test.go | 4 ++-- rollup/cmd/gas_oracle/app/app.go | 7 +++--- .../controller/relayer/relayer_test.go | 2 +- rollup/internal/controller/sender/sender.go | 4 +++- .../internal/controller/sender/sender_test.go | 2 +- .../internal/controller/watcher/l1_watcher.go | 24 +++++++++++++------ .../controller/watcher/l1_watcher_test.go | 4 ++-- rollup/tests/bridge_test.go | 11 +++++---- rollup/tests/gas_oracle_test.go | 4 ++-- tests/integration-test/contracts_test.go | 4 ++-- tests/integration-test/integration_test.go | 2 +- 12 files changed, 52 insertions(+), 32 deletions(-) diff --git a/common/testcontainers/testcontainers.go b/common/testcontainers/testcontainers.go index fa93b1385c..4ec4040912 100644 --- a/common/testcontainers/testcontainers.go +++ b/common/testcontainers/testcontainers.go @@ -168,12 +168,16 @@ func (t *TestcontainerApps) GetPoSL1EndPoint() (string, error) { } // GetPoSL1Client returns a ethclient by dialing running PoS L1 client -func (t *TestcontainerApps) GetPoSL1Client() (*ethclient.Client, error) { +func (t *TestcontainerApps) GetPoSL1Client() (*rpc.Client, *ethclient.Client, error) { endpoint, err := t.GetPoSL1EndPoint() if err != nil { - return nil, err + return nil, nil, err + } + rpcClient, err := rpc.Dial(endpoint) + if err != nil { + return nil, nil, fmt.Errorf("failed to dial L1 rpc endpoint, endpoint: %s, err: %w", endpoint, err) } - return ethclient.Dial(endpoint) + return rpcClient, ethclient.NewClient(rpcClient), nil } // GetDBEndPoint returns the endpoint of the running postgres container @@ -220,13 +224,13 @@ func (t *TestcontainerApps) GetGormDBClient() (*gorm.DB, error) { } // GetL2GethClient returns a ethclient by dialing running L2Geth -func (t *TestcontainerApps) GetL2GethClient() (*ethclient.Client, error) { +func (t *TestcontainerApps) GetL2GethClient() (*rpc.Client, *ethclient.Client, error) { rpcCli, err := t.GetL2Client() if err != nil { - return nil, err + return nil, nil, err } - return ethclient.NewClient(rpcCli), nil + return rpcCli, ethclient.NewClient(rpcCli), nil } // GetL2GethClient returns a rpc client by dialing running L2Geth diff --git a/common/testcontainers/testcontainers_test.go b/common/testcontainers/testcontainers_test.go index 52271fb1cf..0bdc2181d6 100644 --- a/common/testcontainers/testcontainers_test.go +++ b/common/testcontainers/testcontainers_test.go @@ -32,7 +32,7 @@ func TestNewTestcontainerApps(t *testing.T) { endpoint, err = testApps.GetL2GethEndPoint() assert.NoError(t, err) assert.NotEmpty(t, endpoint) - ethclient, err = testApps.GetL2GethClient() + _, ethclient, err = testApps.GetL2GethClient() assert.NoError(t, err) assert.NotNil(t, ethclient) @@ -40,7 +40,7 @@ func TestNewTestcontainerApps(t *testing.T) { endpoint, err = testApps.GetPoSL1EndPoint() assert.NoError(t, err) assert.NotEmpty(t, endpoint) - ethclient, err = testApps.GetPoSL1Client() + _, ethclient, err = testApps.GetPoSL1Client() assert.NoError(t, err) assert.NotNil(t, ethclient) diff --git a/rollup/cmd/gas_oracle/app/app.go b/rollup/cmd/gas_oracle/app/app.go index ec3a9738ae..3c14826712 100644 --- a/rollup/cmd/gas_oracle/app/app.go +++ b/rollup/cmd/gas_oracle/app/app.go @@ -66,12 +66,13 @@ func action(ctx *cli.Context) error { registry := prometheus.DefaultRegisterer observability.Server(ctx, db) - l1client, err := ethclient.Dial(cfg.L1Config.Endpoint) + l1RpcClient, err := rpc.Dial(cfg.L1Config.Endpoint) if err != nil { - log.Crit("failed to connect l1 geth", "config file", cfgFile, "error", err) + log.Crit("failed to dial raw RPC client to L1 endpoint", "endpoint", cfg.L1Config.Endpoint, "error", err) } + l1client := ethclient.NewClient(l1RpcClient) - l1watcher := watcher.NewL1WatcherClient(ctx.Context, l1client, cfg.L1Config.StartHeight, db, registry) + l1watcher := watcher.NewL1WatcherClient(ctx.Context, l1RpcClient, cfg.L1Config.StartHeight, db, registry) l1relayer, err := relayer.NewLayer1Relayer(ctx.Context, db, cfg.L1Config.RelayerConfig, relayer.ServiceTypeL1GasOracle, registry) if err != nil { diff --git a/rollup/internal/controller/relayer/relayer_test.go b/rollup/internal/controller/relayer/relayer_test.go index 322a259aed..53118c75cc 100644 --- a/rollup/internal/controller/relayer/relayer_test.go +++ b/rollup/internal/controller/relayer/relayer_test.go @@ -74,7 +74,7 @@ func setupEnv(t *testing.T) { cfg.L2Config.RelayerConfig.ChainMonitor.BaseURL = "http://localhost:" + svrPort // Create l2geth client. - l2Cli, err = testApps.GetL2GethClient() + _, l2Cli, err = testApps.GetL2GethClient() assert.NoError(t, err) templateBlockTrace1, err := os.ReadFile("../../../testdata/blockTrace_02.json") diff --git a/rollup/internal/controller/sender/sender.go b/rollup/internal/controller/sender/sender.go index 8c754cee6b..8582e64e23 100644 --- a/rollup/internal/controller/sender/sender.go +++ b/rollup/internal/controller/sender/sender.go @@ -67,7 +67,7 @@ type FeeData struct { // Sender Transaction sender to send transaction to l1/l2 type Sender struct { config *config.SenderConfig - rpcClient *rpc.Client // Raw RPC Client + rpcClient *rpc.Client // Raw RPC client gethClient *gethclient.Client // Client to use for CreateAccessList client *ethclient.Client // The client to retrieve on chain data (read-only) writeClients []*ethclient.Client // The clients to send transactions to (write operations) @@ -844,6 +844,8 @@ func (s *Sender) getBlockNumberAndTimestampAndBaseFeeAndBlobFee(ctx context.Cont // Leave it up to the L1 node to return the correct blob base fee. // Previously we would compute it locally using `CalcBlobFee`, but // that needs to be in sync with the L1 node's configuration. + // Note: The fetched blob base fee might not correspond to the block + // that we fetched in the previous step, but this is acceptable. var hex hexutil.Big if err := s.rpcClient.CallContext(ctx, &hex, "eth_blobBaseFee"); err != nil { return 0, 0, 0, 0, fmt.Errorf("failed to call eth_blobBaseFee, err: %w", err) diff --git a/rollup/internal/controller/sender/sender_test.go b/rollup/internal/controller/sender/sender_test.go index 3417d7e410..4e73ab253e 100644 --- a/rollup/internal/controller/sender/sender_test.go +++ b/rollup/internal/controller/sender/sender_test.go @@ -94,7 +94,7 @@ func setupEnv(t *testing.T) { assert.NoError(t, err) assert.NoError(t, migrate.ResetDB(sqlDB)) - l1Client, err := testApps.GetPoSL1Client() + _, l1Client, err := testApps.GetPoSL1Client() assert.NoError(t, err) chainID, err := l1Client.ChainID(context.Background()) diff --git a/rollup/internal/controller/watcher/l1_watcher.go b/rollup/internal/controller/watcher/l1_watcher.go index 1b67089a3a..510c362494 100644 --- a/rollup/internal/controller/watcher/l1_watcher.go +++ b/rollup/internal/controller/watcher/l1_watcher.go @@ -3,13 +3,15 @@ package watcher import ( "context" "errors" + "fmt" "math/big" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/prometheus/client_golang/prometheus" - "github.com/scroll-tech/go-ethereum/consensus/misc" gethTypes "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/ethclient" "github.com/scroll-tech/go-ethereum/log" + "github.com/scroll-tech/go-ethereum/rpc" "gorm.io/gorm" "scroll-tech/common/types" @@ -20,7 +22,8 @@ import ( // L1WatcherClient will listen for smart contract events from Eth L1. type L1WatcherClient struct { ctx context.Context - client *ethclient.Client + rpcClient *rpc.Client // Raw RPC client + client *ethclient.Client // Go SDK RPC client l1BlockOrm *orm.L1Block // The height of the block that the watcher has retrieved header rlp @@ -30,7 +33,7 @@ type L1WatcherClient struct { } // NewL1WatcherClient returns a new instance of L1WatcherClient. -func NewL1WatcherClient(ctx context.Context, client *ethclient.Client, startHeight uint64, db *gorm.DB, reg prometheus.Registerer) *L1WatcherClient { +func NewL1WatcherClient(ctx context.Context, rpcClient *rpc.Client, startHeight uint64, db *gorm.DB, reg prometheus.Registerer) *L1WatcherClient { l1BlockOrm := orm.NewL1Block(db) savedL1BlockHeight, err := l1BlockOrm.GetLatestL1BlockHeight(ctx) if err != nil { @@ -43,7 +46,8 @@ func NewL1WatcherClient(ctx context.Context, client *ethclient.Client, startHeig return &L1WatcherClient{ ctx: ctx, - client: client, + rpcClient: rpcClient, + client: ethclient.NewClient(rpcClient), l1BlockOrm: l1BlockOrm, processedBlockHeight: savedL1BlockHeight, @@ -78,10 +82,16 @@ func (w *L1WatcherClient) FetchBlockHeader(blockHeight uint64) error { baseFee = block.BaseFee.Uint64() } - var blobBaseFee uint64 - if excess := block.ExcessBlobGas; excess != nil { - blobBaseFee = misc.CalcBlobFee(*excess).Uint64() + // Leave it up to the L1 node to return the correct blob base fee. + // Previously we would compute it locally using `CalcBlobFee`, but + // that needs to be in sync with the L1 node's configuration. + // Note: The fetched blob base fee might not correspond to the block + // that we fetched in the previous step, but this is acceptable. + var hex hexutil.Big + if err := w.rpcClient.CallContext(w.ctx, &hex, "eth_blobBaseFee"); err != nil { + return fmt.Errorf("failed to call eth_blobBaseFee, err: %w", err) } + blobBaseFee := hex.ToInt().Uint64() l1Block := orm.L1Block{ Number: blockHeight, diff --git a/rollup/internal/controller/watcher/l1_watcher_test.go b/rollup/internal/controller/watcher/l1_watcher_test.go index 3183b4e346..b530692b1f 100644 --- a/rollup/internal/controller/watcher/l1_watcher_test.go +++ b/rollup/internal/controller/watcher/l1_watcher_test.go @@ -21,10 +21,10 @@ import ( func setupL1Watcher(t *testing.T) (*L1WatcherClient, *gorm.DB) { db := setupDB(t) - client, err := testApps.GetPoSL1Client() + rawClient, _, err := testApps.GetPoSL1Client() assert.NoError(t, err) l1Cfg := cfg.L1Config - watcher := NewL1WatcherClient(context.Background(), client, l1Cfg.StartHeight, db, nil) + watcher := NewL1WatcherClient(context.Background(), rawClient, l1Cfg.StartHeight, db, nil) return watcher, db } diff --git a/rollup/tests/bridge_test.go b/rollup/tests/bridge_test.go index 837e1a054d..4bb37de1a7 100644 --- a/rollup/tests/bridge_test.go +++ b/rollup/tests/bridge_test.go @@ -19,6 +19,7 @@ import ( "github.com/scroll-tech/go-ethereum/crypto" "github.com/scroll-tech/go-ethereum/ethclient" "github.com/scroll-tech/go-ethereum/log" + "github.com/scroll-tech/go-ethereum/rpc" "github.com/stretchr/testify/assert" "gorm.io/gorm" @@ -37,8 +38,10 @@ var ( rollupApp *bcmd.MockApp // clients - l1Client *ethclient.Client - l2Client *ethclient.Client + l1RawClient *rpc.Client + l1Client *ethclient.Client + l2RawClient *rpc.Client + l2Client *ethclient.Client l1Auth *bind.TransactOpts l2Auth *bind.TransactOpts @@ -91,9 +94,9 @@ func setupEnv(t *testing.T) { assert.NoError(t, testApps.StartPoSL1Container()) rollupApp = bcmd.NewRollupApp(testApps, "../conf/config.json") - l1Client, err = testApps.GetPoSL1Client() + l1RawClient, l1Client, err = testApps.GetPoSL1Client() assert.NoError(t, err) - l2Client, err = testApps.GetL2GethClient() + l2RawClient, l2Client, err = testApps.GetL2GethClient() assert.NoError(t, err) l1GethChainID, err = l1Client.ChainID(context.Background()) assert.NoError(t, err) diff --git a/rollup/tests/gas_oracle_test.go b/rollup/tests/gas_oracle_test.go index 1cace3bd11..e069b26baf 100644 --- a/rollup/tests/gas_oracle_test.go +++ b/rollup/tests/gas_oracle_test.go @@ -36,7 +36,7 @@ func testImportL1GasPrice(t *testing.T) { // Create L1Watcher startHeight, err := l1Client.BlockNumber(context.Background()) assert.NoError(t, err) - l1Watcher := watcher.NewL1WatcherClient(context.Background(), l1Client, startHeight-1, db, nil) + l1Watcher := watcher.NewL1WatcherClient(context.Background(), l1RawClient, startHeight-1, db, nil) // fetch new blocks number, err := l1Client.BlockNumber(context.Background()) @@ -110,7 +110,7 @@ func testImportDefaultL1GasPriceDueToL1GasPriceSpike(t *testing.T) { // Create L1Watcher startHeight, err := l1Client.BlockNumber(context.Background()) assert.NoError(t, err) - l1Watcher := watcher.NewL1WatcherClient(context.Background(), l1Client, startHeight-2, db, nil) + l1Watcher := watcher.NewL1WatcherClient(context.Background(), l1RawClient, startHeight-2, db, nil) // fetch new blocks number, err := l1Client.BlockNumber(context.Background()) diff --git a/tests/integration-test/contracts_test.go b/tests/integration-test/contracts_test.go index 4483f394bf..01c47e902f 100644 --- a/tests/integration-test/contracts_test.go +++ b/tests/integration-test/contracts_test.go @@ -24,7 +24,7 @@ func testERC20(t *testing.T) { assert.NoError(t, testApps.StartL2GethContainer()) time.Sleep(time.Second * 3) - l2Cli, err := testApps.GetL2GethClient() + _, l2Cli, err := testApps.GetL2GethClient() assert.Nil(t, err) token, err := erc20.NewERC20Mock(erc20Address, l2Cli) @@ -63,7 +63,7 @@ func testERC20(t *testing.T) { func testGreeter(t *testing.T) { assert.NoError(t, testApps.StartL2GethContainer()) - l2Cli, err := testApps.GetL2GethClient() + _, l2Cli, err := testApps.GetL2GethClient() assert.Nil(t, err) chainID, err := l2Cli.ChainID(context.Background()) diff --git a/tests/integration-test/integration_test.go b/tests/integration-test/integration_test.go index 28527e0247..ec8548970d 100644 --- a/tests/integration-test/integration_test.go +++ b/tests/integration-test/integration_test.go @@ -79,7 +79,7 @@ func testCoordinatorProverInteraction(t *testing.T) { l2BlockOrm := orm.NewL2Block(db) // Connect to l2geth client - l2Client, err := testApps.GetL2GethClient() + _, l2Client, err := testApps.GetL2GethClient() if err != nil { log.Fatalf("Failed to connect to the l2geth client: %v", err) } From 5976ba7d0f4eab3bf9d956497324e8ad7e5c2371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 27 Nov 2025 08:37:50 +0100 Subject: [PATCH 07/10] update dependency --- rollup/internal/controller/sender/sender.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rollup/internal/controller/sender/sender.go b/rollup/internal/controller/sender/sender.go index 8582e64e23..ab1f5980f8 100644 --- a/rollup/internal/controller/sender/sender.go +++ b/rollup/internal/controller/sender/sender.go @@ -10,10 +10,10 @@ import ( "sync" "time" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/holiman/uint256" "github.com/prometheus/client_golang/prometheus" "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/common/hexutil" gethTypes "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/crypto/kzg4844" "github.com/scroll-tech/go-ethereum/ethclient" From eab42296cf5f847952e0ab849ee4487016583a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 27 Nov 2025 08:42:18 +0100 Subject: [PATCH 08/10] cleanup --- common/testcontainers/testcontainers.go | 1 - rollup/tests/bridge_test.go | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/common/testcontainers/testcontainers.go b/common/testcontainers/testcontainers.go index 4ec4040912..23c66cf712 100644 --- a/common/testcontainers/testcontainers.go +++ b/common/testcontainers/testcontainers.go @@ -225,7 +225,6 @@ func (t *TestcontainerApps) GetGormDBClient() (*gorm.DB, error) { // GetL2GethClient returns a ethclient by dialing running L2Geth func (t *TestcontainerApps) GetL2GethClient() (*rpc.Client, *ethclient.Client, error) { - rpcCli, err := t.GetL2Client() if err != nil { return nil, nil, err diff --git a/rollup/tests/bridge_test.go b/rollup/tests/bridge_test.go index 4bb37de1a7..daa87827cf 100644 --- a/rollup/tests/bridge_test.go +++ b/rollup/tests/bridge_test.go @@ -40,7 +40,6 @@ var ( // clients l1RawClient *rpc.Client l1Client *ethclient.Client - l2RawClient *rpc.Client l2Client *ethclient.Client l1Auth *bind.TransactOpts @@ -96,7 +95,7 @@ func setupEnv(t *testing.T) { l1RawClient, l1Client, err = testApps.GetPoSL1Client() assert.NoError(t, err) - l2RawClient, l2Client, err = testApps.GetL2GethClient() + _, l2Client, err = testApps.GetL2GethClient() assert.NoError(t, err) l1GethChainID, err = l1Client.ChainID(context.Background()) assert.NoError(t, err) From 0894618814c93b20865452b2d9d2e7a967c4c23a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 27 Nov 2025 16:01:39 +0100 Subject: [PATCH 09/10] comment fixes --- common/testcontainers/testcontainers.go | 4 ++-- rollup/internal/controller/sender/sender.go | 4 ++-- rollup/internal/controller/watcher/l1_watcher.go | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/testcontainers/testcontainers.go b/common/testcontainers/testcontainers.go index 23c66cf712..e2c224b251 100644 --- a/common/testcontainers/testcontainers.go +++ b/common/testcontainers/testcontainers.go @@ -167,7 +167,7 @@ func (t *TestcontainerApps) GetPoSL1EndPoint() (string, error) { return contrainer.PortEndpoint(context.Background(), "8545/tcp", "http") } -// GetPoSL1Client returns a ethclient by dialing running PoS L1 client +// GetPoSL1Client returns a raw rpc client and an ethclient by dialing the L1 node func (t *TestcontainerApps) GetPoSL1Client() (*rpc.Client, *ethclient.Client, error) { endpoint, err := t.GetPoSL1EndPoint() if err != nil { @@ -223,7 +223,7 @@ func (t *TestcontainerApps) GetGormDBClient() (*gorm.DB, error) { return database.InitDB(dbCfg) } -// GetL2GethClient returns a ethclient by dialing running L2Geth +// GetL2GethClient returns a raw rpc client and an ethclient by dialing the L2 node func (t *TestcontainerApps) GetL2GethClient() (*rpc.Client, *ethclient.Client, error) { rpcCli, err := t.GetL2Client() if err != nil { diff --git a/rollup/internal/controller/sender/sender.go b/rollup/internal/controller/sender/sender.go index ab1f5980f8..282c8be582 100644 --- a/rollup/internal/controller/sender/sender.go +++ b/rollup/internal/controller/sender/sender.go @@ -841,9 +841,9 @@ func (s *Sender) getBlockNumberAndTimestampAndBaseFeeAndBlobFee(ctx context.Cont baseFee = header.BaseFee.Uint64() } - // Leave it up to the L1 node to return the correct blob base fee. + // Leave it up to the L1 node to compute the correct blob base fee. // Previously we would compute it locally using `CalcBlobFee`, but - // that needs to be in sync with the L1 node's configuration. + // that approach requires syncing any future L1 configuration changes. // Note: The fetched blob base fee might not correspond to the block // that we fetched in the previous step, but this is acceptable. var hex hexutil.Big diff --git a/rollup/internal/controller/watcher/l1_watcher.go b/rollup/internal/controller/watcher/l1_watcher.go index 510c362494..ed2b69e665 100644 --- a/rollup/internal/controller/watcher/l1_watcher.go +++ b/rollup/internal/controller/watcher/l1_watcher.go @@ -6,8 +6,8 @@ import ( "fmt" "math/big" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/prometheus/client_golang/prometheus" + "github.com/scroll-tech/go-ethereum/common/hexutil" gethTypes "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/ethclient" "github.com/scroll-tech/go-ethereum/log" @@ -82,9 +82,9 @@ func (w *L1WatcherClient) FetchBlockHeader(blockHeight uint64) error { baseFee = block.BaseFee.Uint64() } - // Leave it up to the L1 node to return the correct blob base fee. + // Leave it up to the L1 node to compute the correct blob base fee. // Previously we would compute it locally using `CalcBlobFee`, but - // that needs to be in sync with the L1 node's configuration. + // that approach requires syncing any future L1 configuration changes. // Note: The fetched blob base fee might not correspond to the block // that we fetched in the previous step, but this is acceptable. var hex hexutil.Big From a20959c8f0c9b2258a628386706bffdf8f9bbf6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 27 Nov 2025 16:15:10 +0100 Subject: [PATCH 10/10] add limits --- rollup/internal/config/relayer.go | 4 ++++ rollup/internal/controller/relayer/l1_relayer.go | 11 +++++++++++ .../controller/relayer/l1_relayer_metrics.go | 15 ++++++++++----- rollup/internal/controller/sender/sender.go | 7 ++++--- rollup/internal/controller/watcher/l1_watcher.go | 7 ++++--- 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/rollup/internal/config/relayer.go b/rollup/internal/config/relayer.go index d82fd861a7..c993b4e3cf 100644 --- a/rollup/internal/config/relayer.go +++ b/rollup/internal/config/relayer.go @@ -109,6 +109,10 @@ type GasOracleConfig struct { L1BaseFeeDefault uint64 `json:"l1_base_fee_default"` L1BlobBaseFeeDefault uint64 `json:"l1_blob_base_fee_default"` + // Limit values for gas oracle updates + L1BaseFeeLimit uint64 `json:"l1_base_fee_limit"` + L1BlobBaseFeeLimit uint64 `json:"l1_blob_base_fee_limit"` + // L1BlobBaseFeeThreshold the threshold of L1 blob base fee to enter the default gas price mode L1BlobBaseFeeThreshold uint64 `json:"l1_blob_base_fee_threshold"` } diff --git a/rollup/internal/controller/relayer/l1_relayer.go b/rollup/internal/controller/relayer/l1_relayer.go index d9f0835d52..da6a19f292 100644 --- a/rollup/internal/controller/relayer/l1_relayer.go +++ b/rollup/internal/controller/relayer/l1_relayer.go @@ -173,6 +173,17 @@ func (r *Layer1Relayer) ProcessGasPriceOracle() { } else if err != nil { return } + // set limit + if baseFee > r.cfg.GasOracleConfig.L1BaseFeeLimit { + log.Error("L1 base fee exceed max limit, set to max limit", "baseFee", baseFee, "maxLimit", r.cfg.GasOracleConfig.L1BaseFeeLimit) + r.metrics.rollupL1RelayerGasPriceOracleFeeOverLimitTotal.Inc() + baseFee = r.cfg.GasOracleConfig.L1BaseFeeLimit + } + if blobBaseFee > r.cfg.GasOracleConfig.L1BlobBaseFeeLimit { + log.Error("L1 blob base fee exceed max limit, set to max limit", "blobBaseFee", blobBaseFee, "maxLimit", r.cfg.GasOracleConfig.L1BlobBaseFeeLimit) + r.metrics.rollupL1RelayerGasPriceOracleFeeOverLimitTotal.Inc() + blobBaseFee = r.cfg.GasOracleConfig.L1BlobBaseFeeLimit + } data, err := r.l1GasOracleABI.Pack("setL1BaseFeeAndBlobBaseFee", new(big.Int).SetUint64(baseFee), new(big.Int).SetUint64(blobBaseFee)) if err != nil { log.Error("Failed to pack setL1BaseFeeAndBlobBaseFee", "block.Hash", block.Hash, "block.Height", block.Number, "block.BaseFee", baseFee, "block.BlobBaseFee", blobBaseFee, "err", err) diff --git a/rollup/internal/controller/relayer/l1_relayer_metrics.go b/rollup/internal/controller/relayer/l1_relayer_metrics.go index 433c373964..0efb6264ee 100644 --- a/rollup/internal/controller/relayer/l1_relayer_metrics.go +++ b/rollup/internal/controller/relayer/l1_relayer_metrics.go @@ -8,11 +8,12 @@ import ( ) type l1RelayerMetrics struct { - rollupL1RelayerGasPriceOraclerRunTotal prometheus.Counter - rollupL1RelayerLatestBaseFee prometheus.Gauge - rollupL1RelayerLatestBlobBaseFee prometheus.Gauge - rollupL1UpdateGasOracleConfirmedTotal prometheus.Counter - rollupL1UpdateGasOracleConfirmedFailedTotal prometheus.Counter + rollupL1RelayerGasPriceOraclerRunTotal prometheus.Counter + rollupL1RelayerLatestBaseFee prometheus.Gauge + rollupL1RelayerLatestBlobBaseFee prometheus.Gauge + rollupL1UpdateGasOracleConfirmedTotal prometheus.Counter + rollupL1UpdateGasOracleConfirmedFailedTotal prometheus.Counter + rollupL1RelayerGasPriceOracleFeeOverLimitTotal prometheus.Counter } var ( @@ -43,6 +44,10 @@ func initL1RelayerMetrics(reg prometheus.Registerer) *l1RelayerMetrics { Name: "rollup_layer1_update_gas_oracle_confirmed_failed_total", Help: "The total number of updating layer1 gas oracle confirmed failed", }), + rollupL1RelayerGasPriceOracleFeeOverLimitTotal: promauto.With(reg).NewCounter(prometheus.CounterOpts{ + Name: "rollup_layer1_gas_price_oracle_fee_over_limit_total", + Help: "The total number of layer1 gas price oracle fee over limit", + }), } }) return l1RelayerMetric diff --git a/rollup/internal/controller/sender/sender.go b/rollup/internal/controller/sender/sender.go index 282c8be582..4f6e959254 100644 --- a/rollup/internal/controller/sender/sender.go +++ b/rollup/internal/controller/sender/sender.go @@ -846,11 +846,12 @@ func (s *Sender) getBlockNumberAndTimestampAndBaseFeeAndBlobFee(ctx context.Cont // that approach requires syncing any future L1 configuration changes. // Note: The fetched blob base fee might not correspond to the block // that we fetched in the previous step, but this is acceptable. - var hex hexutil.Big - if err := s.rpcClient.CallContext(ctx, &hex, "eth_blobBaseFee"); err != nil { + var blobBaseFeeHex hexutil.Big + if err := s.rpcClient.CallContext(ctx, &blobBaseFeeHex, "eth_blobBaseFee"); err != nil { return 0, 0, 0, 0, fmt.Errorf("failed to call eth_blobBaseFee, err: %w", err) } - blobBaseFee := hex.ToInt().Uint64() + // A correct L1 node could not return a value that overflows uint64 + blobBaseFee := blobBaseFeeHex.ToInt().Uint64() // header.Number.Uint64() returns the pendingBlockNumber, so we minus 1 to get the latestBlockNumber. return header.Number.Uint64() - 1, header.Time, baseFee, blobBaseFee, nil diff --git a/rollup/internal/controller/watcher/l1_watcher.go b/rollup/internal/controller/watcher/l1_watcher.go index ed2b69e665..7bd7dc8c76 100644 --- a/rollup/internal/controller/watcher/l1_watcher.go +++ b/rollup/internal/controller/watcher/l1_watcher.go @@ -87,11 +87,12 @@ func (w *L1WatcherClient) FetchBlockHeader(blockHeight uint64) error { // that approach requires syncing any future L1 configuration changes. // Note: The fetched blob base fee might not correspond to the block // that we fetched in the previous step, but this is acceptable. - var hex hexutil.Big - if err := w.rpcClient.CallContext(w.ctx, &hex, "eth_blobBaseFee"); err != nil { + var blobBaseFeeHex hexutil.Big + if err := w.rpcClient.CallContext(w.ctx, &blobBaseFeeHex, "eth_blobBaseFee"); err != nil { return fmt.Errorf("failed to call eth_blobBaseFee, err: %w", err) } - blobBaseFee := hex.ToInt().Uint64() + // A correct L1 node could not return a value that overflows uint64 + blobBaseFee := blobBaseFeeHex.ToInt().Uint64() l1Block := orm.L1Block{ Number: blockHeight,