Skip to content

Commit f591585

Browse files
authored
Merge pull request #314 from uprendis/feature/reactive-gas-oracle
Reactive gas price oracle
2 parents 42bb963 + 54a672f commit f591585

17 files changed

+726
-246
lines changed

ethapi/api.go

Lines changed: 9 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import (
5050
"github.com/Fantom-foundation/go-opera/evmcore"
5151
"github.com/Fantom-foundation/go-opera/gossip/gasprice"
5252
"github.com/Fantom-foundation/go-opera/opera"
53-
"github.com/Fantom-foundation/go-opera/utils/piecefunc"
5453
"github.com/Fantom-foundation/go-opera/utils/signers/gsignercache"
5554
"github.com/Fantom-foundation/go-opera/utils/signers/internaltx"
5655
)
@@ -72,21 +71,15 @@ func NewPublicEthereumAPI(b Backend) *PublicEthereumAPI {
7271

7372
// GasPrice returns a suggestion for a gas price for legacy transactions.
7473
func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
75-
tipcap, err := s.b.SuggestGasTipCap(ctx)
76-
if err != nil {
77-
return nil, err
78-
}
74+
tipcap := s.b.SuggestGasTipCap(ctx, gasprice.AsDefaultCertainty)
7975
tipcap.Add(tipcap, s.b.MinGasPrice())
8076
return (*hexutil.Big)(tipcap), nil
8177
}
8278

8379
// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic fee transactions.
8480
func (s *PublicEthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) {
85-
tipcap, err := s.b.SuggestGasTipCap(ctx)
86-
if err != nil {
87-
return nil, err
88-
}
89-
return (*hexutil.Big)(tipcap), err
81+
tipcap := s.b.SuggestGasTipCap(ctx, gasprice.AsDefaultCertainty)
82+
return (*hexutil.Big)(tipcap), nil
9083
}
9184

9285
type feeHistoryResult struct {
@@ -96,40 +89,6 @@ type feeHistoryResult struct {
9689
GasUsedRatio []float64 `json:"gasUsedRatio"`
9790
}
9891

99-
func scaleGasTip(tip, baseFee *big.Int, ratio uint64) *big.Int {
100-
// max((SuggestedGasTip+minGasPrice)*0.6-minGasPrice, 0)
101-
min := baseFee
102-
est := new(big.Int).Set(tip)
103-
est.Add(est, min)
104-
est.Mul(est, new(big.Int).SetUint64(ratio))
105-
est.Div(est, gasprice.DecimalUnitBn)
106-
est.Sub(est, min)
107-
if est.Sign() < 0 {
108-
return new(big.Int)
109-
}
110-
111-
return est
112-
}
113-
114-
var tipScaleRatio = piecefunc.NewFunc([]piecefunc.Dot{
115-
{
116-
X: 0,
117-
Y: 0.7 * gasprice.DecimalUnit,
118-
},
119-
{
120-
X: 0.2 * gasprice.DecimalUnit,
121-
Y: 1.0 * gasprice.DecimalUnit,
122-
},
123-
{
124-
X: 0.8 * gasprice.DecimalUnit,
125-
Y: 1.2 * gasprice.DecimalUnit,
126-
},
127-
{
128-
X: 1.0 * gasprice.DecimalUnit,
129-
Y: 2.0 * gasprice.DecimalUnit,
130-
},
131-
})
132-
13392
var errInvalidPercentile = errors.New("invalid reward percentile")
13493

13594
func (s *PublicEthereumAPI) FeeHistory(ctx context.Context, blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) {
@@ -166,16 +125,11 @@ func (s *PublicEthereumAPI) FeeHistory(ctx context.Context, blockCount rpc.Decim
166125
}
167126

168127
baseFee := s.b.MinGasPrice()
169-
goldTip, err := s.b.SuggestGasTipCap(ctx)
170-
if err != nil {
171-
return nil, err
172-
}
173128

174129
tips := make([]*hexutil.Big, 0, len(rewardPercentiles))
175130
for _, p := range rewardPercentiles {
176-
ratio := tipScaleRatio(uint64(gasprice.DecimalUnit * p / 100.0))
177-
scaledTip := scaleGasTip(goldTip, baseFee, ratio)
178-
tips = append(tips, (*hexutil.Big)(scaledTip))
131+
tip := s.b.SuggestGasTipCap(ctx, uint64(gasprice.DecimalUnit*p/100.0))
132+
tips = append(tips, (*hexutil.Big)(tip))
179133
}
180134
res.OldestBlock.ToInt().SetUint64(uint64(oldest))
181135
for i := uint64(0); i < uint64(last-oldest+1); i++ {
@@ -186,6 +140,10 @@ func (s *PublicEthereumAPI) FeeHistory(ctx context.Context, blockCount rpc.Decim
186140
return res, nil
187141
}
188142

143+
func (s *PublicEthereumAPI) EffectiveBaseFee(ctx context.Context) *hexutil.Big {
144+
return (*hexutil.Big)(s.b.EffectiveMinGasPrice(ctx))
145+
}
146+
189147
// Syncing returns true if node is syncing
190148
func (s *PublicEthereumAPI) Syncing() (interface{}, error) {
191149
progress := s.b.Progress()

ethapi/backend.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ type PeerProgress struct {
5353
type Backend interface {
5454
// General Ethereum API
5555
Progress() PeerProgress
56-
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
56+
SuggestGasTipCap(ctx context.Context, certainty uint64) *big.Int
57+
EffectiveMinGasPrice(ctx context.Context) *big.Int
5758
ChainDb() ethdb.Database
5859
AccountManager() *accounts.Manager
5960
ExtRPCEnabled() bool

ethapi/transaction_args.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import (
2929
"github.com/ethereum/go-ethereum/core/types"
3030
"github.com/ethereum/go-ethereum/log"
3131
"github.com/ethereum/go-ethereum/rpc"
32+
33+
"github.com/Fantom-foundation/go-opera/gossip/gasprice"
3234
)
3335

3436
// TransactionArgs represents the arguments to construct a new transaction
@@ -86,10 +88,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
8688
// In this clause, user left some fields unspecified.
8789
if b.ChainConfig().IsLondon(head.Number) && args.GasPrice == nil {
8890
if args.MaxPriorityFeePerGas == nil {
89-
tip, err := b.SuggestGasTipCap(ctx)
90-
if err != nil {
91-
return err
92-
}
91+
tip := b.SuggestGasTipCap(ctx, gasprice.AsDefaultCertainty)
9392
args.MaxPriorityFeePerGas = (*hexutil.Big)(tip)
9493
}
9594
if args.MaxFeePerGas == nil {
@@ -107,10 +106,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
107106
return errors.New("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet")
108107
}
109108
if args.GasPrice == nil {
110-
price, err := b.SuggestGasTipCap(ctx)
111-
if err != nil {
112-
return err
113-
}
109+
price := b.SuggestGasTipCap(ctx, gasprice.AsDefaultCertainty)
114110
price.Add(price, b.MinGasPrice())
115111
args.GasPrice = (*hexutil.Big)(price)
116112
}

eventcheck/gaspowercheck/gas_power_check.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func calcGasPower(e inter.EventI, selfParent inter.EventI, ctx *ValidationContex
107107
}
108108

109109
func CalcValidatorGasPower(e inter.EventI, eTime, prevTime inter.Timestamp, prevGasPowerLeft uint64, validators *pos.Validators, config Config) uint64 {
110-
gasPowerPerSec, maxGasPower, startup := calcValidatorGasPowerPerSec(e.Creator(), validators, config)
110+
gasPowerPerSec, maxGasPower, startup := CalcValidatorGasPowerPerSec(e.Creator(), validators, config)
111111

112112
if e.SelfParent() == nil {
113113
if prevGasPowerLeft < startup {
@@ -131,7 +131,7 @@ func CalcValidatorGasPower(e inter.EventI, eTime, prevTime inter.Timestamp, prev
131131
return gasPower
132132
}
133133

134-
func calcValidatorGasPowerPerSec(
134+
func CalcValidatorGasPowerPerSec(
135135
validator idx.ValidatorID,
136136
validators *pos.Validators,
137137
config Config,

evmcore/tx_pool.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ type StateReader interface {
139139
GetBlock(hash common.Hash, number uint64) *EvmBlock
140140
StateAt(root common.Hash) (*state.StateDB, error)
141141
MinGasPrice() *big.Int
142-
RecommendedGasTip() *big.Int
142+
EffectiveMinTip() *big.Int
143143
MaxGasLimit() uint64
144144
SubscribeNewBlock(ch chan<- ChainHeadNotify) notify.Subscription
145145
Config() *params.ChainConfig
@@ -555,6 +555,17 @@ func (pool *TxPool) Pending(enforceTips bool) (map[common.Address]types.Transact
555555
return pending, nil
556556
}
557557

558+
func (pool *TxPool) PendingSlice() types.Transactions {
559+
pool.mu.Lock()
560+
defer pool.mu.Unlock()
561+
562+
pending := make(types.Transactions, 0, 1000)
563+
for _, list := range pool.pending {
564+
pending = append(pending, list.Flatten()...)
565+
}
566+
return pending
567+
}
568+
558569
func (pool *TxPool) SampleHashes(max int) []common.Hash {
559570
return pool.all.SampleHashes(max)
560571
}
@@ -629,7 +640,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
629640
return ErrUnderpriced
630641
}
631642
// Ensure Opera-specific hard bounds
632-
if recommendedGasTip, minPrice := pool.chain.RecommendedGasTip(), pool.chain.MinGasPrice(); recommendedGasTip != nil && minPrice != nil {
643+
if recommendedGasTip, minPrice := pool.chain.EffectiveMinTip(), pool.chain.MinGasPrice(); recommendedGasTip != nil && minPrice != nil {
633644
if tx.GasTipCapIntCmp(recommendedGasTip) < 0 {
634645
return ErrUnderpriced
635646
}

evmcore/tx_pool_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func (bc *testBlockChain) CurrentBlock() *EvmBlock {
8282
func (bc *testBlockChain) MinGasPrice() *big.Int {
8383
return common.Big0
8484
}
85-
func (bc *testBlockChain) RecommendedGasTip() *big.Int {
85+
func (bc *testBlockChain) EffectiveMinTip() *big.Int {
8686
return nil
8787
}
8888
func (bc *testBlockChain) MaxGasLimit() uint64 {

gossip/config.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,9 @@ func DefaultConfig(scale cachescale.Func) Config {
200200
},
201201

202202
GPO: gasprice.Config{
203-
MaxTipCap: gasprice.DefaultMaxTipCap,
204-
MinTipCap: new(big.Int),
205-
MaxTipCapMultiplierRatio: big.NewInt(25 * gasprice.DecimalUnit),
206-
MiddleTipCapMultiplierRatio: big.NewInt(3.75 * gasprice.DecimalUnit),
207-
GasPowerWallRatio: big.NewInt(0.05 * gasprice.DecimalUnit),
203+
MaxGasPrice: gasprice.DefaultMaxGasPrice,
204+
MinGasPrice: new(big.Int),
205+
DefaultCertainty: 0.5 * gasprice.DecimalUnit,
208206
},
209207

210208
RPCBlockExt: true,

gossip/dummy_tx_pool.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ func (p *dummyTxPool) Pending(enforceTips bool) (map[common.Address]types.Transa
7676
return batches, nil
7777
}
7878

79+
func (p *dummyTxPool) PendingSlice() types.Transactions {
80+
p.lock.RLock()
81+
defer p.lock.RUnlock()
82+
83+
return append(make(types.Transactions, 0, len(p.pool)), p.pool...)
84+
}
85+
7986
func (p *dummyTxPool) SubscribeNewTxsNotify(ch chan<- evmcore.NewTxsNotify) notify.Subscription {
8087
return p.txFeed.Subscribe(ch)
8188
}

gossip/ethapi_backend.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,12 @@ func (b *EthAPIBackend) TxPoolContentFrom(addr common.Address) (types.Transactio
424424
return b.svc.txpool.ContentFrom(addr)
425425
}
426426

427-
func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
428-
return b.svc.gpo.SuggestTipCap(), nil
427+
func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context, certainty uint64) *big.Int {
428+
return b.svc.gpo.SuggestTip(certainty)
429+
}
430+
431+
func (b *EthAPIBackend) EffectiveMinGasPrice(ctx context.Context) *big.Int {
432+
return b.svc.gpo.EffectiveMinGasPrice()
429433
}
430434

431435
func (b *EthAPIBackend) ChainDb() ethdb.Database {

gossip/evm_state_reader.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@ import (
1515
"github.com/Fantom-foundation/go-opera/opera"
1616
)
1717

18-
var (
19-
big3 = big.NewInt(3)
20-
big5 = big.NewInt(5)
21-
)
22-
2318
type EvmStateReader struct {
2419
*ServiceFeed
2520

@@ -46,14 +41,10 @@ func (r *EvmStateReader) MinGasPrice() *big.Int {
4641
return r.store.GetRules().Economy.MinGasPrice
4742
}
4843

49-
// RecommendedGasTip returns current soft lower bound for gas tip
50-
func (r *EvmStateReader) RecommendedGasTip() *big.Int {
51-
// max((SuggestedGasTip+minGasPrice)*0.6-minGasPrice, 0)
44+
// EffectiveMinTip returns current soft lower bound for gas tip
45+
func (r *EvmStateReader) EffectiveMinTip() *big.Int {
5246
min := r.MinGasPrice()
53-
est := new(big.Int).Set(r.gpo.SuggestTipCap())
54-
est.Add(est, min)
55-
est.Mul(est, big3)
56-
est.Div(est, big5)
47+
est := r.gpo.EffectiveMinGasPrice()
5748
est.Sub(est, min)
5849
if est.Sign() < 0 {
5950
return new(big.Int)

gossip/gasprice/constructive.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package gasprice
2+
3+
import (
4+
"math/big"
5+
6+
"github.com/Fantom-foundation/go-opera/utils/piecefunc"
7+
)
8+
9+
func (gpo *Oracle) maxTotalGasPower() *big.Int {
10+
rules := gpo.backend.GetRules()
11+
12+
allocBn := new(big.Int).SetUint64(rules.Economy.LongGasPower.AllocPerSec)
13+
periodBn := new(big.Int).SetUint64(uint64(rules.Economy.LongGasPower.MaxAllocPeriod))
14+
maxTotalGasPowerBn := new(big.Int).Mul(allocBn, periodBn)
15+
maxTotalGasPowerBn.Div(maxTotalGasPowerBn, secondBn)
16+
return maxTotalGasPowerBn
17+
}
18+
19+
func (gpo *Oracle) effectiveMinGasPrice() *big.Int {
20+
return gpo.constructiveGasPrice(0, 0, gpo.backend.GetRules().Economy.MinGasPrice)
21+
}
22+
23+
func (gpo *Oracle) constructiveGasPrice(gasOffestAbs uint64, gasOffestRatio uint64, adjustedMinPrice *big.Int) *big.Int {
24+
max := gpo.maxTotalGasPower()
25+
26+
current64 := gpo.backend.TotalGasPowerLeft()
27+
if current64 > gasOffestAbs {
28+
current64 -= gasOffestAbs
29+
} else {
30+
current64 = 0
31+
}
32+
current := new(big.Int).SetUint64(current64)
33+
34+
freeRatioBn := current.Mul(current, DecimalUnitBn)
35+
freeRatioBn.Div(freeRatioBn, max)
36+
freeRatio := freeRatioBn.Uint64()
37+
if freeRatio > gasOffestRatio {
38+
freeRatio -= gasOffestRatio
39+
} else {
40+
freeRatio = 0
41+
}
42+
if freeRatio > DecimalUnit {
43+
freeRatio = DecimalUnit
44+
}
45+
v := gpo.constructiveGasPriceOf(freeRatio, adjustedMinPrice)
46+
return v
47+
}
48+
49+
var freeRatioToConstructiveGasRatio = piecefunc.NewFunc([]piecefunc.Dot{
50+
{
51+
X: 0,
52+
Y: 25 * DecimalUnit,
53+
},
54+
{
55+
X: 0.3 * DecimalUnit,
56+
Y: 9 * DecimalUnit,
57+
},
58+
{
59+
X: 0.5 * DecimalUnit,
60+
Y: 3.75 * DecimalUnit,
61+
},
62+
{
63+
X: 0.8 * DecimalUnit,
64+
Y: 1.5 * DecimalUnit,
65+
},
66+
{
67+
X: 0.95 * DecimalUnit,
68+
Y: 1.05 * DecimalUnit,
69+
},
70+
{
71+
X: DecimalUnit,
72+
Y: DecimalUnit,
73+
},
74+
})
75+
76+
func (gpo *Oracle) constructiveGasPriceOf(freeRatio uint64, adjustedMinPrice *big.Int) *big.Int {
77+
multiplier := new(big.Int).SetUint64(freeRatioToConstructiveGasRatio(freeRatio))
78+
79+
// gas price = multiplier * adjustedMinPrice
80+
price := multiplier.Mul(multiplier, adjustedMinPrice)
81+
return price.Div(price, DecimalUnitBn)
82+
}

0 commit comments

Comments
 (0)