-
Notifications
You must be signed in to change notification settings - Fork 626
feat(gas-oracle): support moving average base fee #1735
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
base: develop
Are you sure you want to change the base?
Changes from all commits
197b0a3
277e71e
ecb00c1
4d9fd55
5004902
94da520
3dd9ab6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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.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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Many places below still use
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think we can add a prefix There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently we update the status to 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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 { | ||
|
@@ -154,16 +151,32 @@ 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) { | ||
// It indicates the committing batch has been stuck for a long time, it's likely that the L1 gas fee spiked. | ||
// 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 | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.