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.GpoPercentileFlag,
|
||||||
utils.GpoMaxGasPriceFlag,
|
utils.GpoMaxGasPriceFlag,
|
||||||
utils.GpoIgnoreGasPriceFlag,
|
utils.GpoIgnoreGasPriceFlag,
|
||||||
|
utils.GpoCongestionThresholdFlag,
|
||||||
|
|
||||||
utils.MinerNotifyFullFlag,
|
utils.MinerNotifyFullFlag,
|
||||||
configFileFlag,
|
configFileFlag,
|
||||||
utils.CatalystFlag,
|
utils.CatalystFlag,
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
|
||||||
utils.GpoPercentileFlag,
|
utils.GpoPercentileFlag,
|
||||||
utils.GpoMaxGasPriceFlag,
|
utils.GpoMaxGasPriceFlag,
|
||||||
utils.GpoIgnoreGasPriceFlag,
|
utils.GpoIgnoreGasPriceFlag,
|
||||||
|
utils.GpoCongestionThresholdFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -735,6 +735,11 @@ var (
|
||||||
Usage: "Gas price below which gpo will ignore transactions",
|
Usage: "Gas price below which gpo will ignore transactions",
|
||||||
Value: ethconfig.Defaults.GPO.IgnorePrice.Int64(),
|
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
|
// Metrics flags
|
||||||
MetricsEnabledFlag = cli.BoolFlag{
|
MetricsEnabledFlag = cli.BoolFlag{
|
||||||
|
|
@ -1429,6 +1434,9 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) {
|
||||||
if ctx.GlobalIsSet(GpoIgnoreGasPriceFlag.Name) {
|
if ctx.GlobalIsSet(GpoIgnoreGasPriceFlag.Name) {
|
||||||
cfg.IgnorePrice = big.NewInt(ctx.GlobalInt64(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) {
|
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 {
|
if gpoParams.Default == nil {
|
||||||
gpoParams.Default = config.Miner.GasPrice
|
gpoParams.Default = config.Miner.GasPrice
|
||||||
}
|
}
|
||||||
|
gpoParams.DefaultBasePrice = new(big.Int).SetUint64(config.TxPool.PriceLimit)
|
||||||
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
|
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
|
||||||
|
|
||||||
// Setup DNS discovery iterators.
|
// Setup DNS discovery iterators.
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ func TestFeeHistory(t *testing.T) {
|
||||||
MaxHeaderHistory: c.maxHeader,
|
MaxHeaderHistory: c.maxHeader,
|
||||||
MaxBlockHistory: c.maxBlock,
|
MaxBlockHistory: c.maxBlock,
|
||||||
}
|
}
|
||||||
backend := newTestBackend(t, big.NewInt(16), c.pending)
|
backend := newTestBackend(t, big.NewInt(16), c.pending, 0)
|
||||||
oracle := NewOracle(backend, config)
|
oracle := NewOracle(backend, config)
|
||||||
|
|
||||||
first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent)
|
first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent)
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ const sampleNumber = 3 // Number of transactions sampled in a block
|
||||||
var (
|
var (
|
||||||
DefaultMaxPrice = big.NewInt(500 * params.GWei)
|
DefaultMaxPrice = big.NewInt(500 * params.GWei)
|
||||||
DefaultIgnorePrice = big.NewInt(2 * params.Wei)
|
DefaultIgnorePrice = big.NewInt(2 * params.Wei)
|
||||||
|
DefaultBasePrice = big.NewInt(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|
@ -49,6 +50,8 @@ type Config struct {
|
||||||
Default *big.Int `toml:",omitempty"`
|
Default *big.Int `toml:",omitempty"`
|
||||||
MaxPrice *big.Int `toml:",omitempty"`
|
MaxPrice *big.Int `toml:",omitempty"`
|
||||||
IgnorePrice *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.
|
// OracleBackend includes all necessary background APIs for oracle.
|
||||||
|
|
@ -60,6 +63,7 @@ type OracleBackend interface {
|
||||||
ChainConfig() *params.ChainConfig
|
ChainConfig() *params.ChainConfig
|
||||||
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
||||||
StateAt(root common.Hash) (*state.StateDB, error)
|
StateAt(root common.Hash) (*state.StateDB, error)
|
||||||
|
Stats() (pending int, queued int)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Oracle recommends gas prices based on the content of recent
|
// Oracle recommends gas prices based on the content of recent
|
||||||
|
|
@ -75,6 +79,8 @@ type Oracle struct {
|
||||||
|
|
||||||
checkBlocks, percentile int
|
checkBlocks, percentile int
|
||||||
maxHeaderHistory, maxBlockHistory 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
|
historyCache *lru.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,6 +122,16 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
|
||||||
maxBlockHistory = 1
|
maxBlockHistory = 1
|
||||||
log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory)
|
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)
|
cache, _ := lru.New(2048)
|
||||||
headEvent := make(chan core.ChainHeadEvent, 1)
|
headEvent := make(chan core.ChainHeadEvent, 1)
|
||||||
|
|
@ -139,6 +155,8 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
|
||||||
percentile: percent,
|
percentile: percent,
|
||||||
maxHeaderHistory: maxHeaderHistory,
|
maxHeaderHistory: maxHeaderHistory,
|
||||||
maxBlockHistory: maxBlockHistory,
|
maxBlockHistory: maxBlockHistory,
|
||||||
|
congestedThreshold: congestedThreshold,
|
||||||
|
defaultBasePrice: defaultBasePrice,
|
||||||
historyCache: cache,
|
historyCache: cache,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -170,6 +188,28 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
|
||||||
if headHash == lastHead {
|
if headHash == lastHead {
|
||||||
return new(big.Int).Set(lastPrice), nil
|
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 (
|
var (
|
||||||
sent, exp int
|
sent, exp int
|
||||||
number = head.Number.Uint64()
|
number = head.Number.Uint64()
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ const testHead = 32
|
||||||
type testBackend struct {
|
type testBackend struct {
|
||||||
chain *core.BlockChain
|
chain *core.BlockChain
|
||||||
pending bool // pending block available
|
pending bool // pending block available
|
||||||
|
pendingTxCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
|
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
|
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 (
|
var (
|
||||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
addr = crypto.PubkeyToAddress(key.PublicKey)
|
addr = crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
|
@ -113,6 +118,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke
|
||||||
config.ShanghaiBlock = londonBlock
|
config.ShanghaiBlock = londonBlock
|
||||||
config.BernoulliBlock = londonBlock
|
config.BernoulliBlock = londonBlock
|
||||||
config.CurieBlock = londonBlock
|
config.CurieBlock = londonBlock
|
||||||
|
config.DescartesBlock = londonBlock
|
||||||
engine := ethash.NewFaker()
|
engine := ethash.NewFaker()
|
||||||
db := rawdb.NewMemoryDatabase()
|
db := rawdb.NewMemoryDatabase()
|
||||||
genesis, err := gspec.Commit(db)
|
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)
|
t.Fatalf("Failed to create local chain, %v", err)
|
||||||
}
|
}
|
||||||
chain.InsertChain(blocks)
|
chain.InsertChain(blocks)
|
||||||
return &testBackend{chain: chain, pending: pending}
|
return &testBackend{chain: chain, pending: pending, pendingTxCount: pendingTxCount}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *testBackend) CurrentHeader() *types.Header {
|
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
|
{big.NewInt(33), big.NewInt(params.GWei * int64(30))}, // Fork point in the future
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
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)
|
oracle := NewOracle(backend, config)
|
||||||
|
|
||||||
// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G
|
// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import (
|
||||||
const (
|
const (
|
||||||
VersionMajor = 5 // Major version component of the current release
|
VersionMajor = 5 // Major version component of the current release
|
||||||
VersionMinor = 3 // Minor 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
|
VersionMeta = "mainnet" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue