Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bbb189a
feat: suggest gas price based on latest block's transactions
yiweichi Jul 13, 2025
0222ec1
remove debug logs
yiweichi Jul 14, 2025
6756f03
remove debug logs
yiweichi Jul 14, 2025
789f9af
rename values
yiweichi Jul 14, 2025
1d8bb8f
typo
yiweichi Jul 14, 2025
99087cc
fix: typo
yiweichi Jul 14, 2025
e9b1937
Merge branch 'develop' into feat-update-gas-price-oracle
yiweichi Jul 14, 2025
ad68317
lint
yiweichi Jul 14, 2025
d3b3d50
fix: ci test
yiweichi Jul 14, 2025
8f14875
update comments
yiweichi Jul 14, 2025
a62b705
Merge branch 'develop' into feat-update-gas-price-oracle
yiweichi Jul 14, 2025
48d9103
chore: auto version bump [bot]
yiweichi Jul 14, 2025
f102c37
fix: feehistory logic
yiweichi Jul 14, 2025
dcbfd5b
Update eth/gasprice/feehistory.go
yiweichi Jul 15, 2025
ef7c70f
address comments
yiweichi Jul 15, 2025
39734a0
address comments
yiweichi Jul 15, 2025
b5c2975
chore: remove used flag
yiweichi Jul 15, 2025
2b08dde
fix: ci
yiweichi Jul 15, 2025
09bf296
fix: test
yiweichi Jul 15, 2025
a788554
fix: lint
yiweichi Jul 15, 2025
f3ddd08
Merge branch 'develop' into feat-update-gas-price-oracle
yiweichi Jul 15, 2025
b80e931
chore: auto version bump [bot]
yiweichi Jul 15, 2025
22505e9
test: add scroll gasprice unit test
yiweichi Jul 15, 2025
95c59a3
address comments
yiweichi Jul 15, 2025
8d3e883
address comments
yiweichi Jul 15, 2025
9601fd9
add benchmark
yiweichi Jul 15, 2025
67f30d4
perf: fee_history also use cache
yiweichi Jul 15, 2025
b4a8ebb
perf: fee_history also use cache
yiweichi Jul 15, 2025
8226689
fix: lastIsCongested lock
yiweichi Jul 16, 2025
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
1 change: 0 additions & 1 deletion cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ var (
utils.GpoPercentileFlag,
utils.GpoMaxGasPriceFlag,
utils.GpoIgnoreGasPriceFlag,
utils.GpoCongestionThresholdFlag,

utils.MinerNotifyFullFlag,
configFileFlag,
Expand Down
1 change: 0 additions & 1 deletion cmd/geth/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,6 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.GpoPercentileFlag,
utils.GpoMaxGasPriceFlag,
utils.GpoIgnoreGasPriceFlag,
utils.GpoCongestionThresholdFlag,
},
},
{
Expand Down
8 changes: 0 additions & 8 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,11 +749,6 @@ var (
Usage: "Gas price below which gpo will ignore transactions",
Value: ethconfig.Defaults.GPO.IgnorePrice.Int64(),
}
GpoCongestionThresholdFlag = cli.IntFlag{
Name: "gpo.congestionthreshold",
Usage: "Number of pending transactions to consider the network congested and suggest a minimum tip cap",
Value: ethconfig.Defaults.GPO.CongestedThreshold,
}
GpoDefaultGasTipCapFlag = cli.Int64Flag{
Name: "gpo.defaultgastipcap",
Usage: "Default minimum gas tip cap (in wei) to be used after Curie fork (EIP-1559) (default: 100)",
Expand Down Expand Up @@ -1552,9 +1547,6 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) {
if ctx.GlobalIsSet(GpoIgnoreGasPriceFlag.Name) {
cfg.IgnorePrice = big.NewInt(ctx.GlobalInt64(GpoIgnoreGasPriceFlag.Name))
}
if ctx.GlobalIsSet(GpoCongestionThresholdFlag.Name) {
cfg.CongestedThreshold = ctx.GlobalInt(GpoCongestionThresholdFlag.Name)
}
if ctx.GlobalIsSet(GpoDefaultGasTipCapFlag.Name) {
cfg.DefaultGasTipCap = big.NewInt(ctx.GlobalInt64(GpoDefaultGasTipCapFlag.Name))
}
Expand Down
18 changes: 7 additions & 11 deletions eth/gasprice/feehistory.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,18 +245,14 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast
}
oldestBlock := lastBlock + 1 - uint64(blocks)

// If pending txs are less than oracle.congestedThreshold, we consider the network to be non-congested and suggest
// a minimal tip cap. This is to prevent users from overpaying for gas when the network is not congested and a few
// high-priced txs are causing the suggested tip cap to be high.
// If the suggestedGasPrice equals to oracle.defaultGasTipCap or oracle.defaultBasePrice (before Curie (EIP-1559)),
// it means the latest block is NOT out of capacity. We consider the network to be non-congested and suggest a minimal
// tip cap. This is to prevent users from overpaying for gas when the network is not congested and a few high-priced
// transactions are causing the suggested tip cap to be high.
var nonCongestedPrice *big.Int
pendingTxCount, _ := oracle.backend.StatsWithMinBaseFee(headHeader.BaseFee)
if pendingTxCount < oracle.congestedThreshold {
// Before Curie (EIP-1559), we need to return the total suggested gas price. After Curie we return defaultGasTipCap wei as the tip cap,
// as the base fee is set separately or added manually for legacy transactions.
nonCongestedPrice = oracle.defaultGasTipCap
if !oracle.backend.ChainConfig().IsCurie(headHeader.Number) {
nonCongestedPrice = oracle.defaultBasePrice
}
suggestedGasPrice, isCongested := oracle.calculateSuggestPriorityFee(ctx, headHeader)
if !isCongested {
nonCongestedPrice = suggestedGasPrice
}

var (
Expand Down
89 changes: 32 additions & 57 deletions eth/gasprice/gasprice.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,15 @@ var (
)

type Config struct {
Blocks int
Percentile int
MaxHeaderHistory int
MaxBlockHistory int
Default *big.Int `toml:",omitempty"`
MaxPrice *big.Int `toml:",omitempty"`
IgnorePrice *big.Int `toml:",omitempty"`
CongestedThreshold int // Number of pending transactions to consider the network congested and suggest a minimum tip cap.
DefaultBasePrice *big.Int `toml:",omitempty"` // Base price to set when CongestedThreshold is reached before Curie (EIP 1559).
DefaultGasTipCap *big.Int `toml:",omitempty"` // Default minimum gas tip cap to use after Curie (EIP 1559).
Blocks int
Percentile int
MaxHeaderHistory int
MaxBlockHistory int
Default *big.Int `toml:",omitempty"`
MaxPrice *big.Int `toml:",omitempty"`
IgnorePrice *big.Int `toml:",omitempty"`
DefaultBasePrice *big.Int `toml:",omitempty"` // Base price to set when CongestedThreshold is reached before Curie (EIP 1559).
DefaultGasTipCap *big.Int `toml:",omitempty"` // Default minimum gas tip cap to use after Curie (EIP 1559).
}

// OracleBackend includes all necessary background APIs for oracle.
Expand All @@ -70,24 +69,22 @@ type OracleBackend interface {
ChainConfig() *params.ChainConfig
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
StateAt(root common.Hash) (*state.StateDB, error)
Stats() (pending int, queued int)
StatsWithMinBaseFee(minBaseFee *big.Int) (pending int, queued int)
}

// Oracle recommends gas prices based on the content of recent
// blocks. Suitable for both light and full clients.
type Oracle struct {
backend OracleBackend
lastHead common.Hash
lastPrice *big.Int
maxPrice *big.Int
ignorePrice *big.Int
cacheLock sync.RWMutex
fetchLock sync.Mutex
backend OracleBackend
lastHead common.Hash
lastPrice *big.Int
lastIsCongested bool
maxPrice *big.Int
ignorePrice *big.Int
cacheLock sync.RWMutex
fetchLock sync.Mutex

checkBlocks, percentile int
maxHeaderHistory, maxBlockHistory int
congestedThreshold int // Number of pending transactions to consider the network congested and suggest a minimum tip cap.
defaultBasePrice *big.Int // Base price to set when CongestedThreshold is reached before Curie (EIP 1559).
defaultGasTipCap *big.Int // Default gas tip cap to suggest after Curie (EIP 1559) when the network is not congested.
historyCache *lru.Cache
Expand Down Expand Up @@ -131,11 +128,6 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
maxBlockHistory = 1
log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory)
}
congestedThreshold := params.CongestedThreshold
if congestedThreshold < 0 {
congestedThreshold = 0
log.Warn("Sanitizing invalid gasprice oracle congested threshold", "provided", params.CongestedThreshold, "updated", congestedThreshold)
}
defaultBasePrice := params.DefaultBasePrice
if defaultBasePrice == nil || defaultBasePrice.Int64() < 0 {
defaultBasePrice = DefaultBasePrice
Expand All @@ -161,18 +153,17 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
}()

return &Oracle{
backend: backend,
lastPrice: params.Default,
maxPrice: maxPrice,
ignorePrice: ignorePrice,
checkBlocks: blocks,
percentile: percent,
maxHeaderHistory: maxHeaderHistory,
maxBlockHistory: maxBlockHistory,
congestedThreshold: congestedThreshold,
defaultBasePrice: defaultBasePrice,
defaultGasTipCap: defaultGasTipCap,
historyCache: cache,
backend: backend,
lastPrice: params.Default,
maxPrice: maxPrice,
ignorePrice: ignorePrice,
checkBlocks: blocks,
percentile: percent,
maxHeaderHistory: maxHeaderHistory,
maxBlockHistory: maxBlockHistory,
defaultBasePrice: defaultBasePrice,
defaultGasTipCap: defaultGasTipCap,
historyCache: cache,
}
}

Expand All @@ -186,6 +177,10 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
headHash := head.Hash()

if oracle.backend.ChainConfig().IsScroll() {
return oracle.SuggestScrollPriorityFee(ctx, head), nil
}

// If the latest gasprice is still available, return it.
oracle.cacheLock.RLock()
lastHead, lastPrice := oracle.lastHead, oracle.lastPrice
Expand All @@ -204,26 +199,6 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
return new(big.Int).Set(lastPrice), nil
}

// If pending txs are less than oracle.congestedThreshold, we consider the network to be non-congested and suggest
// a minimal tip cap. This is to prevent users from overpaying for gas when the network is not congested and a few
// high-priced txs are causing the suggested tip cap to be high.
pendingTxCount, _ := oracle.backend.StatsWithMinBaseFee(head.BaseFee)
if pendingTxCount < oracle.congestedThreshold {
// Before Curie (EIP-1559), we need to return the total suggested gas price. After Curie we return defaultGasTipCap wei as the tip cap,
// as the base fee is set separately or added manually for legacy transactions.
price := oracle.defaultGasTipCap
if !oracle.backend.ChainConfig().IsCurie(head.Number) {
price = oracle.defaultBasePrice
}

oracle.cacheLock.Lock()
oracle.lastHead = headHash
oracle.lastPrice = price
oracle.cacheLock.Unlock()

return new(big.Int).Set(price), nil
}

var (
sent, exp int
number = head.Number.Uint64()
Expand Down
60 changes: 0 additions & 60 deletions eth/gasprice/gasprice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,63 +210,3 @@ func TestSuggestTipCap(t *testing.T) {
}
}
}

func TestSuggestTipCapCongestedThreshold(t *testing.T) {
expectedDefaultBasePricePreCurie := big.NewInt(2000)
expectedDefaultBasePricePostCurie := big.NewInt(100)

config := Config{
Blocks: 3,
Percentile: 60,
Default: big.NewInt(params.GWei),
CongestedThreshold: 50,
DefaultBasePrice: expectedDefaultBasePricePreCurie,
}
var cases = []struct {
fork *big.Int // London fork number
pendingTx int // Number of pending transactions in the mempool
expect *big.Int // Expected gasprice suggestion
}{
{nil, 0, expectedDefaultBasePricePreCurie}, // No congestion - default base price
{nil, 49, expectedDefaultBasePricePreCurie}, // No congestion - default base price
{nil, 50, big.NewInt(params.GWei * int64(30))}, // Congestion - normal behavior
{nil, 100, big.NewInt(params.GWei * int64(30))}, // Congestion - normal behavior

// Fork point in genesis
{big.NewInt(0), 0, expectedDefaultBasePricePostCurie}, // No congestion - default base price
{big.NewInt(0), 49, expectedDefaultBasePricePostCurie}, // No congestion - default base price
{big.NewInt(0), 50, big.NewInt(params.GWei * int64(30))}, // Congestion - normal behavior
{big.NewInt(0), 100, big.NewInt(params.GWei * int64(30))}, // Congestion - normal behavior

// Fork point in first block
{big.NewInt(1), 0, expectedDefaultBasePricePostCurie}, // No congestion - default base price
{big.NewInt(1), 49, expectedDefaultBasePricePostCurie}, // No congestion - default base price
{big.NewInt(1), 50, big.NewInt(params.GWei * int64(30))}, // Congestion - normal behavior
{big.NewInt(1), 100, big.NewInt(params.GWei * int64(30))}, // Congestion - normal behavior

// Fork point in last block
{big.NewInt(32), 0, expectedDefaultBasePricePostCurie}, // No congestion - default base price
{big.NewInt(32), 49, expectedDefaultBasePricePostCurie}, // No congestion - default base price
{big.NewInt(32), 50, big.NewInt(params.GWei * int64(30))}, // Congestion - normal behavior
{big.NewInt(32), 100, big.NewInt(params.GWei * int64(30))}, // Congestion - normal behavior

// Fork point in the future
{big.NewInt(33), 0, expectedDefaultBasePricePreCurie}, // No congestion - default base price
{big.NewInt(33), 49, expectedDefaultBasePricePreCurie}, // No congestion - default base price
{big.NewInt(33), 50, big.NewInt(params.GWei * int64(30))}, // Congestion - normal behavior
{big.NewInt(33), 100, big.NewInt(params.GWei * int64(30))}, // Congestion - normal behavior
}
for _, c := range cases {
backend := newTestBackend(t, c.fork, false, c.pendingTx)
oracle := NewOracle(backend, config)

// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G
got, err := oracle.SuggestTipCap(context.Background())
if err != nil {
t.Fatalf("Failed to retrieve recommended gas price: %v", err)
}
if got.Cmp(c.expect) != 0 {
t.Fatalf("Gas price mismatch, want %d, got %d", c.expect, got)
}
}
}
Loading
Loading