Compare commits

...

7 Commits

Author SHA1 Message Date
Morty
3dd9ab6c35 address comment 2025-09-11 23:12:19 +08:00
Morty
94da520beb Merge branch 'feat-gpo-moving-average' of https://github.com/scroll-tech/scroll into feat-gpo-moving-average 2025-09-11 03:04:45 +08:00
Morty
5004902063 address comments 2025-09-11 03:04:35 +08:00
yiweichi
4d9fd5530e chore: auto version bump [bot] 2025-09-10 18:58:17 +00:00
Morty
ecb00c1390 Merge branch 'develop' into feat-gpo-moving-average 2025-09-11 02:58:03 +08:00
yiweichi
277e71efec chore: auto version bump [bot] 2025-09-08 12:00:53 +00:00
Morty
197b0a3dd8 feat(gas-oracle): support moving average base fee 2025-09-08 19:56:28 +08:00
5 changed files with 92 additions and 20 deletions

View File

@@ -5,7 +5,7 @@ import (
"runtime/debug"
)
var tag = "v4.5.46"
var tag = "v4.5.47"
var commit = func() string {
if info, ok := debug.ReadBuildInfo(); ok {

View File

@@ -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",

View File

@@ -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

View File

@@ -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)
limit := r.cfg.GasOracleConfig.CalculateAverageFeesWindowSize
blocks, err := r.l1BlockOrm.GetLatestL1Blocks(r.ctx, limit)
if err != nil {
log.Warn("Failed to fetch latest L1 block height from db", "err", err)
log.Error("Failed to GetLatestL1Blocks from db", "limit", limit, "err", err)
return
}
blocks, err := r.l1BlockOrm.GetL1Blocks(r.ctx, map[string]interface{}{
"number": latestBlockHeight,
})
if err != nil {
log.Error("Failed to GetL1Blocks from db", "height", latestBlockHeight, "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.Warn("No l1 blocks to process", "limit", limit)
return
}
block := blocks[0]
if types.GasOracleStatus(block.GasOracleStatus) == types.GasOraclePending {
@@ -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)
// 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 {
@@ -154,8 +151,24 @@ func (r *Layer1Relayer) ProcessGasPriceOracle() {
log.Error("Invalid exchange rate", "exchangeRate", exchangeRate)
return
}
baseFee = uint64(math.Ceil(float64(baseFee) / exchangeRate))
blobBaseFee = uint64(math.Ceil(float64(blobBaseFee) / exchangeRate))
// Check for overflow in exchange rate calculation
adjustedBaseFee := math.Ceil(float64(baseFee) / exchangeRate)
adjustedBlobBaseFee := math.Ceil(float64(blobBaseFee) / exchangeRate)
if adjustedBaseFee > float64(^uint64(0)) {
log.Error("Base fee overflow after exchange rate adjustment", "originalBaseFee", baseFee, "exchangeRate", exchangeRate, "adjustedBaseFee", adjustedBaseFee)
baseFee = ^uint64(0) // Set to max uint64
} else {
baseFee = uint64(adjustedBaseFee)
}
if adjustedBlobBaseFee > float64(^uint64(0)) {
log.Error("Blob base fee overflow after exchange rate adjustment", "originalBlobBaseFee", blobBaseFee, "exchangeRate", exchangeRate, "adjustedBlobBaseFee", adjustedBlobBaseFee)
blobBaseFee = ^uint64(0) // Set to max uint64
} else {
blobBaseFee = uint64(adjustedBlobBaseFee)
}
}
if r.shouldUpdateGasOracle(baseFee, blobBaseFee) {
@@ -163,7 +176,7 @@ func (r *Layer1Relayer) ProcessGasPriceOracle() {
// If we are not committing batches due to high fees then we shouldn't update fees to prevent users from paying high l1_data_fee
// Also, set fees to some default value, because we have already updated fees to some high values, probably
var reachTimeout bool
if reachTimeout, err = r.commitBatchReachTimeout(); reachTimeout && block.BlobBaseFee > r.cfg.GasOracleConfig.L1BlobBaseFeeThreshold && err == nil {
if reachTimeout, err = r.commitBatchReachTimeout(); reachTimeout && blobBaseFee > r.cfg.GasOracleConfig.L1BlobBaseFeeThreshold && err == nil {
if r.lastBaseFee == r.cfg.GasOracleConfig.L1BaseFeeDefault && r.lastBlobBaseFee == r.cfg.GasOracleConfig.L1BlobBaseFeeDefault {
return
}
@@ -175,13 +188,13 @@ func (r *Layer1Relayer) ProcessGasPriceOracle() {
}
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)
log.Error("Failed to pack setL1BaseFeeAndBlobBaseFee", "block.Hash", block.Hash, "block.Height", block.Number, "baseFee", baseFee, "blobBaseFee", blobBaseFee, "err", err)
return
}
txHash, _, err := r.gasOracleSender.SendTransaction(block.Hash, &r.cfg.GasPriceOracleContractAddress, data, nil)
if err != nil {
log.Error("Failed to send gas oracle update tx to layer2", "block.Hash", block.Hash, "block.Height", block.Number, "block.BaseFee", baseFee, "block.BlobBaseFee", blobBaseFee, "err", err)
log.Error("Failed to send gas oracle update tx to layer2", "block.Hash", block.Hash, "block.Height", block.Number, "baseFee", baseFee, "blobBaseFee", blobBaseFee, "err", err)
return
}
@@ -287,3 +300,44 @@ 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.
// Uses big.Int for intermediate calculations to avoid overflow.
func (r *Layer1Relayer) calculateAverageFees(blocks []orm.L1Block) (avgBaseFee uint64, avgBlobBaseFee uint64) {
if len(blocks) == 0 {
return 0, 0
}
// Use big.Int to handle large sums without overflow
totalBaseFee := big.NewInt(0)
totalBlobBaseFee := big.NewInt(0)
count := big.NewInt(int64(len(blocks)))
for _, b := range blocks {
totalBaseFee.Add(totalBaseFee, big.NewInt(0).SetUint64(b.BaseFee))
totalBlobBaseFee.Add(totalBlobBaseFee, big.NewInt(0).SetUint64(b.BlobBaseFee))
}
// Calculate averages
avgBaseFeeBig := big.NewInt(0).Div(totalBaseFee, count)
avgBlobBaseFeeBig := big.NewInt(0).Div(totalBlobBaseFee, count)
// Check if results fit in uint64
maxUint64 := big.NewInt(0).SetUint64(^uint64(0))
if avgBaseFeeBig.Cmp(maxUint64) > 0 {
log.Error("Average base fee exceeds uint64 max, capping at max value", "calculatedAvg", avgBaseFeeBig.String())
avgBaseFee = ^uint64(0)
} else {
avgBaseFee = avgBaseFeeBig.Uint64()
}
if avgBlobBaseFeeBig.Cmp(maxUint64) > 0 {
log.Error("Average blob base fee exceeds uint64 max, capping at max value", "calculatedAvg", avgBlobBaseFeeBig.String())
avgBlobBaseFee = ^uint64(0)
} else {
avgBlobBaseFee = avgBlobBaseFeeBig.Uint64()
}
return avgBaseFee, avgBlobBaseFee
}

View File

@@ -71,6 +71,20 @@ 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
}
// 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) {