Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion common/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"runtime/debug"
)

var tag = "v4.5.45"
var tag = "v4.5.46"

var commit = func() string {
if info, ok := debug.ReadBuildInfo(); ok {
Expand Down
3 changes: 2 additions & 1 deletion rollup/conf/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"check_committed_batches_window_minutes": 5,
"l1_base_fee_default": 15000000000,
"l1_blob_base_fee_default": 1,
"l1_blob_base_fee_threshold": 0
"l1_blob_base_fee_threshold": 0,
"calculate_average_fees_window_size": 100
},
"gas_oracle_sender_signer_config": {
"signer_type": "PrivateKey",
Expand Down
3 changes: 3 additions & 0 deletions rollup/internal/config/relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ type GasOracleConfig struct {

// L1BlobBaseFeeThreshold the threshold of L1 blob base fee to enter the default gas price mode
L1BlobBaseFeeThreshold uint64 `json:"l1_blob_base_fee_threshold"`

// CalculateAverageFeesWindowSize the number of blocks used for average fee calculation
CalculateAverageFeesWindowSize int `json:"calculate_average_fees_window_size"`
}

// SignerConfig - config of signer, contains type and config corresponding to type
Expand Down
39 changes: 26 additions & 13 deletions rollup/internal/controller/relayer/l1_relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,23 +105,20 @@ func NewLayer1Relayer(ctx context.Context, db *gorm.DB, cfg *config.RelayerConfi
// ProcessGasPriceOracle imports gas price to layer2
func (r *Layer1Relayer) ProcessGasPriceOracle() {
r.metrics.rollupL1RelayerGasPriceOraclerRunTotal.Inc()
latestBlockHeight, err := r.l1BlockOrm.GetLatestL1BlockHeight(r.ctx)
if err != nil {
log.Warn("Failed to fetch latest L1 block height from db", "err", err)
return
}

blocks, err := r.l1BlockOrm.GetL1Blocks(r.ctx, map[string]interface{}{
"number": latestBlockHeight,
})
limit := r.cfg.GasOracleConfig.CalculateAverageFeesWindowSize
blocks, err := r.l1BlockOrm.GetLatestL1Blocks(r.ctx, limit)
if err != nil {
log.Error("Failed to GetL1Blocks from db", "height", latestBlockHeight, "err", err)
log.Error("Failed to GetLatestL1Blocks from db", "limit", limit, "err", err)
return
}
if len(blocks) != 1 {
log.Error("Block not exist", "height", latestBlockHeight)

// nothing to do if we don't have any l1 blocks
if len(blocks) == 0 {
log.Info("No l1 blocks to process", "limit", limit)
return
}

block := blocks[0]
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this the oldest or newest block?

Copy link
Member Author

Choose a reason for hiding this comment

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

it's the newest block.


if types.GasOracleStatus(block.GasOracleStatus) == types.GasOraclePending {
Expand All @@ -130,8 +127,8 @@ func (r *Layer1Relayer) ProcessGasPriceOracle() {
return
}

baseFee := block.BaseFee
blobBaseFee := block.BlobBaseFee
// calculate the average base fee and blob base fee of the last N blocks
baseFee, blobBaseFee := r.calculateAverageFees(blocks)
Copy link
Contributor

Choose a reason for hiding this comment

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

Many places below still use block.

  • Threshold should be checked against blobBaseFee, not block.BlobBaseFee
  • Send tx should probably not use block.Hash for ID (risk of conflict with other txs)
  • Need to consider DB updates, including gas oracle import status

Copy link
Member Author

@yiweichi yiweichi Sep 10, 2025

Choose a reason for hiding this comment

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

Send tx should probably not use block.Hash for ID (risk of conflict with other txs)

I think we can add a prefix updateL1GasOracle- before block.Hash. btw, just want to check this is an already existing risk, instead of introducing by this PR right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Need to consider DB updates, including gas oracle import status

I'm not sure what does that mean, I think we already update import status here and here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Currently we update the status to GasOracleImporting and then to GasOracleImported, ensuring that we import the same L1 block info at most once.

If your intention that we keep exactly the same logic, but now instead of meaning "this block is being imported / has been imported", it will mean that "the avg price with the window up to this block is being imported / has been imported"?

In that case, my points 2 and 3 above can be ignored. But please double check that this will work as intended.

Copy link
Member Author

@yiweichi yiweichi Sep 11, 2025

Choose a reason for hiding this comment

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

it will mean that "the avg price with the window up to this block is being imported / has been imported"?

Yes, that's my intention. We only care about if the newest block of the window has been imported/importing or not. If it has, we just skip it, and waiting for the next new l1 block.

I think this will work as intended.


// include the token exchange rate in the fee data if alternative gas token enabled
if r.cfg.GasOracleConfig.AlternativeGasTokenConfig != nil && r.cfg.GasOracleConfig.AlternativeGasTokenConfig.Enabled {
Expand Down Expand Up @@ -287,3 +284,19 @@ func (r *Layer1Relayer) commitBatchReachTimeout() (bool, error) {
// Because batches[0].CommittedAt is nil in this case, this will only continue for a short time window.
return len(batches) == 0 || (batches[0].Index != 0 && batches[0].CommittedAt != nil && utils.NowUTC().Sub(*batches[0].CommittedAt) > time.Duration(r.cfg.GasOracleConfig.CheckCommittedBatchesWindowMinutes)*time.Minute), nil
}

// calculateAverageFees returns the average base fee and blob base fee.
func (r *Layer1Relayer) calculateAverageFees(blocks []orm.L1Block) (avgBaseFee uint64, avgBlobBaseFee uint64) {
if len(blocks) == 0 {
return 0, 0
}

var totalBaseFee, totalBlobBaseFee uint64
for _, b := range blocks {
totalBaseFee += b.BaseFee
totalBlobBaseFee += b.BlobBaseFee
}

count := uint64(len(blocks))
return totalBaseFee / count, totalBlobBaseFee / count
}
40 changes: 40 additions & 0 deletions rollup/internal/orm/l1_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,46 @@ func (o *L1Block) GetL1Blocks(ctx context.Context, fields map[string]interface{}
return l1Blocks, nil
}

// GetLatestL1Blocks get the latest N l1 blocks ordered by block number descending
func (o *L1Block) GetLatestL1Blocks(ctx context.Context, limit int) ([]L1Block, error) {
db := o.db.WithContext(ctx)
db = db.Model(&L1Block{})
db = db.Order("number DESC")
db = db.Limit(limit)

var l1Blocks []L1Block
if err := db.Find(&l1Blocks).Error; err != nil {
return nil, fmt.Errorf("L1Block.GetLatestL1Blocks error: %w, limit: %d", err, limit)
}
return l1Blocks, nil
}

// GetAverageFees returns the average base_fee and blob_base_fee of all blocks
func (o *L1Block) GetAverageFees(ctx context.Context) (avgBaseFee float64, avgBlobBaseFee float64, err error) {
db := o.db.WithContext(ctx)

var l1Blocks []L1Block
if err := db.Model(&L1Block{}).Find(&l1Blocks).Error; err != nil {
return 0, 0, fmt.Errorf("L1Block.GetAverageFees error: %w", err)
}

if len(l1Blocks) == 0 {
return 0, 0, nil
}

var totalBaseFee, totalBlobBaseFee uint64
for _, block := range l1Blocks {
totalBaseFee += block.BaseFee
totalBlobBaseFee += block.BlobBaseFee
}

count := float64(len(l1Blocks))
avgBaseFee = float64(totalBaseFee) / count
avgBlobBaseFee = float64(totalBlobBaseFee) / count

return avgBaseFee, avgBlobBaseFee, nil
}

// GetBlobFeesInRange returns all blob_base_fee values for blocks
// with number ∈ [startBlock..endBlock], ordered by block number ascending.
func (o *L1Block) GetBlobFeesInRange(ctx context.Context, startBlock, endBlock uint64) ([]uint64, error) {
Expand Down