mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 05:36:46 +00:00
feat: congestion-aware gas price oracle (#790)
* Implement functionality to reset gas price / suggested tip to minimal value when there's no congestion * Add flags to configure congestion value and initialize gas price oracle accordingly * Fix and add tests to make sure GPO works as expected depending on pre- or post-Curie (EIP 1559) upgrade * Apply review suggestions * chore: auto version bump [bot] --------- Co-authored-by: omerfirmak <omerfirmak@users.noreply.github.com>
This commit is contained in:
parent
9ec83a509a
commit
b9d3c72e0d
8 changed files with 141 additions and 23 deletions
|
|
@ -159,6 +159,8 @@ var (
|
|||
utils.GpoPercentileFlag,
|
||||
utils.GpoMaxGasPriceFlag,
|
||||
utils.GpoIgnoreGasPriceFlag,
|
||||
utils.GpoCongestionThresholdFlag,
|
||||
|
||||
utils.MinerNotifyFullFlag,
|
||||
configFileFlag,
|
||||
utils.CatalystFlag,
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
|
|||
utils.GpoPercentileFlag,
|
||||
utils.GpoMaxGasPriceFlag,
|
||||
utils.GpoIgnoreGasPriceFlag,
|
||||
utils.GpoCongestionThresholdFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -735,6 +735,11 @@ 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,
|
||||
}
|
||||
|
||||
// Metrics flags
|
||||
MetricsEnabledFlag = cli.BoolFlag{
|
||||
|
|
@ -1429,6 +1434,9 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) {
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client sync_service.EthCl
|
|||
if gpoParams.Default == nil {
|
||||
gpoParams.Default = config.Miner.GasPrice
|
||||
}
|
||||
gpoParams.DefaultBasePrice = new(big.Int).SetUint64(config.TxPool.PriceLimit)
|
||||
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
|
||||
|
||||
// Setup DNS discovery iterators.
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ func TestFeeHistory(t *testing.T) {
|
|||
MaxHeaderHistory: c.maxHeader,
|
||||
MaxBlockHistory: c.maxBlock,
|
||||
}
|
||||
backend := newTestBackend(t, big.NewInt(16), c.pending)
|
||||
backend := newTestBackend(t, big.NewInt(16), c.pending, 0)
|
||||
oracle := NewOracle(backend, config)
|
||||
|
||||
first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent)
|
||||
|
|
|
|||
|
|
@ -39,16 +39,19 @@ const sampleNumber = 3 // Number of transactions sampled in a block
|
|||
var (
|
||||
DefaultMaxPrice = big.NewInt(500 * params.GWei)
|
||||
DefaultIgnorePrice = big.NewInt(2 * params.Wei)
|
||||
DefaultBasePrice = big.NewInt(0)
|
||||
)
|
||||
|
||||
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"`
|
||||
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).
|
||||
}
|
||||
|
||||
// OracleBackend includes all necessary background APIs for oracle.
|
||||
|
|
@ -60,6 +63,7 @@ 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)
|
||||
}
|
||||
|
||||
// Oracle recommends gas prices based on the content of recent
|
||||
|
|
@ -75,6 +79,8 @@ type Oracle struct {
|
|||
|
||||
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).
|
||||
historyCache *lru.Cache
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +122,16 @@ 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
|
||||
log.Warn("Sanitizing invalid gasprice oracle default base price", "provided", params.DefaultBasePrice, "updated", defaultBasePrice)
|
||||
}
|
||||
|
||||
cache, _ := lru.New(2048)
|
||||
headEvent := make(chan core.ChainHeadEvent, 1)
|
||||
|
|
@ -131,15 +147,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,
|
||||
historyCache: cache,
|
||||
backend: backend,
|
||||
lastPrice: params.Default,
|
||||
maxPrice: maxPrice,
|
||||
ignorePrice: ignorePrice,
|
||||
checkBlocks: blocks,
|
||||
percentile: percent,
|
||||
maxHeaderHistory: maxHeaderHistory,
|
||||
maxBlockHistory: maxBlockHistory,
|
||||
congestedThreshold: congestedThreshold,
|
||||
defaultBasePrice: defaultBasePrice,
|
||||
historyCache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -170,6 +188,28 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
|
|||
if headHash == lastHead {
|
||||
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.Stats()
|
||||
if pendingTxCount < oracle.congestedThreshold {
|
||||
// Before Curie (EIP-1559), we need to return the total suggested gas price. After Curie we return 1 wei as the tip cap,
|
||||
// as the base fee is set separately or added manually for legacy transactions.
|
||||
// Set price to 1 as otherwise tx with a 0 tip might be filtered out by the default mempool config.
|
||||
price := big.NewInt(1)
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -38,8 +38,9 @@ import (
|
|||
const testHead = 32
|
||||
|
||||
type testBackend struct {
|
||||
chain *core.BlockChain
|
||||
pending bool // pending block available
|
||||
chain *core.BlockChain
|
||||
pending bool // pending block available
|
||||
pendingTxCount int
|
||||
}
|
||||
|
||||
func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
|
||||
|
|
@ -96,7 +97,11 @@ func (b *testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) eve
|
|||
return nil
|
||||
}
|
||||
|
||||
func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBackend {
|
||||
func (b *testBackend) Stats() (int, int) {
|
||||
return b.pendingTxCount, 0
|
||||
}
|
||||
|
||||
func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool, pendingTxCount int) *testBackend {
|
||||
var (
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr = crypto.PubkeyToAddress(key.PublicKey)
|
||||
|
|
@ -113,6 +118,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke
|
|||
config.ShanghaiBlock = londonBlock
|
||||
config.BernoulliBlock = londonBlock
|
||||
config.CurieBlock = londonBlock
|
||||
config.DescartesBlock = londonBlock
|
||||
engine := ethash.NewFaker()
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
genesis, err := gspec.Commit(db)
|
||||
|
|
@ -154,7 +160,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke
|
|||
t.Fatalf("Failed to create local chain, %v", err)
|
||||
}
|
||||
chain.InsertChain(blocks)
|
||||
return &testBackend{chain: chain, pending: pending}
|
||||
return &testBackend{chain: chain, pending: pending, pendingTxCount: pendingTxCount}
|
||||
}
|
||||
|
||||
func (b *testBackend) CurrentHeader() *types.Header {
|
||||
|
|
@ -186,7 +192,67 @@ func TestSuggestTipCap(t *testing.T) {
|
|||
{big.NewInt(33), big.NewInt(params.GWei * int64(30))}, // Fork point in the future
|
||||
}
|
||||
for _, c := range cases {
|
||||
backend := newTestBackend(t, c.fork, false)
|
||||
backend := newTestBackend(t, c.fork, false, 0)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSuggestTipCapCongestedThreshold(t *testing.T) {
|
||||
expectedDefaultBasePricePreCurie := big.NewInt(2000)
|
||||
expectedDefaultBasePricePostCurie := big.NewInt(1)
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import (
|
|||
const (
|
||||
VersionMajor = 5 // Major version component of the current release
|
||||
VersionMinor = 3 // Minor version component of the current release
|
||||
VersionPatch = 34 // Patch version component of the current release
|
||||
VersionPatch = 35 // Patch version component of the current release
|
||||
VersionMeta = "mainnet" // Version metadata to append to the version string
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue