Skip to content

Commit e8a9807

Browse files
authored
Merge pull request #732 from gzliudan/gas-price
eth/gasprice: fix wrong gas price with empty blocks
2 parents cb2d604 + 59b06e4 commit e8a9807

File tree

7 files changed

+91
-93
lines changed

7 files changed

+91
-93
lines changed

common/types.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package common
1818

1919
import (
20+
"bytes"
2021
"encoding/hex"
2122
"fmt"
2223
"math/big"
@@ -77,23 +78,44 @@ type Vote struct {
7778
Voter Address
7879
}
7980

81+
// BytesToHash sets b to hash.
82+
// If b is larger than len(h), b will be cropped from the left.
8083
func BytesToHash(b []byte) Hash {
8184
var h Hash
8285
h.SetBytes(b)
8386
return h
8487
}
88+
8589
func StringToHash(s string) Hash { return BytesToHash([]byte(s)) }
86-
func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) }
90+
91+
// BigToHash sets byte representation of b to hash.
92+
// If b is larger than len(h), b will be cropped from the left.
93+
func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) }
94+
8795
func Uint64ToHash(b uint64) Hash { return BytesToHash(new(big.Int).SetUint64(b).Bytes()) }
88-
func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) }
96+
97+
// HexToHash sets byte representation of s to hash.
98+
// If b is larger than len(h), b will be cropped from the left.
99+
func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) }
100+
101+
// Cmp compares two hashes.
102+
func (h Hash) Cmp(other Hash) int {
103+
return bytes.Compare(h[:], other[:])
104+
}
89105

90106
// IsZero returns if a Hash is empty
91107
func (h Hash) IsZero() bool { return h == Hash{} }
92108

93109
// Get the string representation of the underlying hash
94110
func (h Hash) Str() string { return string(h[:]) }
111+
112+
// Bytes gets the byte representation of the underlying hash.
95113
func (h Hash) Bytes() []byte { return h[:] }
114+
115+
// Big converts a hash to a big integer.
96116
func (h Hash) Big() *big.Int { return new(big.Int).SetBytes(h[:]) }
117+
118+
// Hex converts a hash to a hex string.
97119
func (h Hash) Hex() string { return hexutil.Encode(h[:]) }
98120

99121
// TerminalString implements log.TerminalStringer, formatting a string for console
@@ -129,7 +151,8 @@ func (h Hash) MarshalText() ([]byte, error) {
129151
return hexutil.Bytes(h[:]).MarshalText()
130152
}
131153

132-
// Sets the hash to the value of b. If b is larger than len(h), 'b' will be cropped (from the left).
154+
// SetBytes sets the hash to the value of b.
155+
// If b is larger than len(h), b will be cropped from the left.
133156
func (h *Hash) SetBytes(b []byte) {
134157
if len(b) > len(h) {
135158
b = b[len(b)-HashLength:]

eth/backend.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,11 +225,7 @@ func New(ctx *node.ServiceContext, config *ethconfig.Config, XDCXServ *XDCx.XDCX
225225
} else {
226226
eth.ApiBackend = &EthApiBackend{eth, nil, nil}
227227
}
228-
gpoParams := config.GPO
229-
if gpoParams.Default == nil {
230-
gpoParams.Default = config.GasPrice
231-
}
232-
eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, gpoParams)
228+
eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, config.GPO, config.GasPrice)
233229

234230
// Set global ipc endpoint.
235231
eth.blockchain.IPCEndpoint = ctx.GetConfig().IPCEndpoint()

eth/gasprice/feehistory.go

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
"fmt"
2424
"math"
2525
"math/big"
26-
"sort"
26+
"slices"
2727
"sync/atomic"
2828

2929
"github.com/XinFinOrg/XDPoSChain/common"
@@ -56,28 +56,22 @@ type blockFees struct {
5656
err error
5757
}
5858

59-
// processedFees contains the results of a processed block and is also used for caching
59+
type cacheKey struct {
60+
number uint64
61+
percentiles string
62+
}
63+
64+
// processedFees contains the results of a processed block.
6065
type processedFees struct {
6166
reward []*big.Int
6267
baseFee, nextBaseFee *big.Int
6368
gasUsedRatio float64
6469
}
6570

6671
// txGasAndReward is sorted in ascending order based on reward
67-
type (
68-
txGasAndReward struct {
69-
gasUsed uint64
70-
reward *big.Int
71-
}
72-
sortGasAndReward []txGasAndReward
73-
)
74-
75-
func (s sortGasAndReward) Len() int { return len(s) }
76-
func (s sortGasAndReward) Swap(i, j int) {
77-
s[i], s[j] = s[j], s[i]
78-
}
79-
func (s sortGasAndReward) Less(i, j int) bool {
80-
return s[i].reward.Cmp(s[j].reward) < 0
72+
type txGasAndReward struct {
73+
gasUsed uint64
74+
reward *big.Int
8175
}
8276

8377
// processBlock takes a blockFees structure with the blockNumber, the header and optionally
@@ -112,12 +106,14 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
112106
return
113107
}
114108

115-
sorter := make(sortGasAndReward, len(bf.block.Transactions()))
109+
sorter := make([]txGasAndReward, len(bf.block.Transactions()))
116110
for i, tx := range bf.block.Transactions() {
117111
reward, _ := tx.EffectiveGasTip(bf.block.BaseFee())
118112
sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward}
119113
}
120-
sort.Stable(sorter)
114+
slices.SortStableFunc(sorter, func(a, b txGasAndReward) int {
115+
return a.reward.Cmp(b.reward)
116+
})
121117

122118
var txIndex int
123119
sumGasUsed := sorter[0].gasUsed
@@ -268,13 +264,10 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL
268264
oracle.processBlock(fees, rewardPercentiles)
269265
results <- fees
270266
} else {
271-
cacheKey := struct {
272-
number uint64
273-
percentiles string
274-
}{blockNumber, string(percentileKey)}
267+
cacheKey := cacheKey{number: blockNumber, percentiles: string(percentileKey)}
275268

276269
if p, ok := oracle.historyCache.Get(cacheKey); ok {
277-
fees.results = p.(processedFees)
270+
fees.results = p
278271
results <- fees
279272
} else {
280273
if len(rewardPercentiles) != 0 {

eth/gasprice/feehistory_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func TestFeeHistory(t *testing.T) {
5858
MaxBlockHistory: c.maxBlock,
5959
}
6060
backend := newTestBackend(t, big.NewInt(16), c.pending)
61-
oracle := NewOracle(backend, config)
61+
oracle := NewOracle(backend, config, nil)
6262

6363
first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent)
6464

eth/gasprice/gasprice.go

Lines changed: 45 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,17 @@ package gasprice
1919
import (
2020
"context"
2121
"math/big"
22-
"sort"
22+
"slices"
2323
"sync"
2424

2525
"github.com/XinFinOrg/XDPoSChain/common"
26+
"github.com/XinFinOrg/XDPoSChain/common/lru"
2627
"github.com/XinFinOrg/XDPoSChain/core"
2728
"github.com/XinFinOrg/XDPoSChain/core/types"
2829
"github.com/XinFinOrg/XDPoSChain/event"
2930
"github.com/XinFinOrg/XDPoSChain/log"
3031
"github.com/XinFinOrg/XDPoSChain/params"
3132
"github.com/XinFinOrg/XDPoSChain/rpc"
32-
lru "github.com/hashicorp/golang-lru"
3333
)
3434

3535
const sampleNumber = 3 // Number of transactions sampled in a block
@@ -44,7 +44,6 @@ type Config struct {
4444
Percentile int
4545
MaxHeaderHistory uint64
4646
MaxBlockHistory uint64
47-
Default *big.Int `toml:",omitempty"`
4847
MaxPrice *big.Int `toml:",omitempty"`
4948
IgnorePrice *big.Int `toml:",omitempty"`
5049
}
@@ -72,12 +71,13 @@ type Oracle struct {
7271

7372
checkBlocks, percentile int
7473
maxHeaderHistory, maxBlockHistory uint64
75-
historyCache *lru.Cache
74+
75+
historyCache *lru.Cache[cacheKey, processedFees]
7676
}
7777

7878
// NewOracle returns a new gasprice oracle which can recommend suitable
7979
// gasprice for newly created transaction.
80-
func NewOracle(backend OracleBackend, params Config) *Oracle {
80+
func NewOracle(backend OracleBackend, params Config, startPrice *big.Int) *Oracle {
8181
blocks := params.Blocks
8282
if blocks < 1 {
8383
blocks = 1
@@ -87,8 +87,7 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
8787
if percent < 0 {
8888
percent = 0
8989
log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
90-
}
91-
if percent > 100 {
90+
} else if percent > 100 {
9291
percent = 100
9392
log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
9493
}
@@ -104,8 +103,21 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
104103
} else if ignorePrice.Int64() > 0 {
105104
log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice)
106105
}
106+
maxHeaderHistory := params.MaxHeaderHistory
107+
if maxHeaderHistory < 1 {
108+
maxHeaderHistory = 1
109+
log.Warn("Sanitizing invalid gasprice oracle max header history", "provided", params.MaxHeaderHistory, "updated", maxHeaderHistory)
110+
}
111+
maxBlockHistory := params.MaxBlockHistory
112+
if maxBlockHistory < 1 {
113+
maxBlockHistory = 1
114+
log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory)
115+
}
116+
if startPrice == nil {
117+
startPrice = new(big.Int)
118+
}
107119

108-
cache, _ := lru.New(2048)
120+
cache := lru.NewCache[cacheKey, processedFees](2048)
109121
headEvent := make(chan core.ChainHeadEvent, 1)
110122
backend.SubscribeChainHeadEvent(headEvent)
111123
go func() {
@@ -120,13 +132,13 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
120132

121133
return &Oracle{
122134
backend: backend,
123-
lastPrice: params.Default,
135+
lastPrice: startPrice,
124136
maxPrice: maxPrice,
125137
ignorePrice: ignorePrice,
126138
checkBlocks: blocks,
127139
percentile: percent,
128-
maxHeaderHistory: params.MaxHeaderHistory,
129-
maxBlockHistory: params.MaxBlockHistory,
140+
maxHeaderHistory: maxHeaderHistory,
141+
maxBlockHistory: maxBlockHistory,
130142
historyCache: cache,
131143
}
132144
}
@@ -166,7 +178,7 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
166178
results []*big.Int
167179
)
168180
for sent < oracle.checkBlocks && number > 0 {
169-
go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
181+
go oracle.getBlockValues(ctx, number, sampleNumber, oracle.ignorePrice, result, quit)
170182
sent++
171183
exp++
172184
number--
@@ -181,15 +193,15 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
181193
// Nothing returned. There are two special cases here:
182194
// - The block is empty
183195
// - All the transactions included are sent by the miner itself.
184-
// In these cases, use the latest calculated price for samping.
196+
// In these cases, use half of the latest calculated price for samping.
185197
if len(res.values) == 0 {
186-
res.values = []*big.Int{lastPrice}
198+
res.values = []*big.Int{new(big.Int).Div(lastPrice, common.Big2)}
187199
}
188200
// Besides, in order to collect enough data for sampling, if nothing
189201
// meaningful returned, try to query more blocks. But the maximum
190202
// is 2*checkBlocks.
191203
if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 {
192-
go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
204+
go oracle.getBlockValues(ctx, number, sampleNumber, oracle.ignorePrice, result, quit)
193205
sent++
194206
exp++
195207
number--
@@ -198,7 +210,7 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
198210
}
199211
price := lastPrice
200212
if len(results) > 0 {
201-
sort.Sort(bigIntArray(results))
213+
slices.SortFunc(results, func(a, b *big.Int) int { return a.Cmp(b) })
202214
price = results[(len(results)-1)*oracle.percentile/100]
203215
}
204216
if price.Cmp(oracle.maxPrice) > 0 {
@@ -226,35 +238,11 @@ type results struct {
226238
err error
227239
}
228240

229-
type txSorter struct {
230-
txs []*types.Transaction
231-
baseFee *big.Int
232-
}
233-
234-
func newSorter(txs []*types.Transaction, baseFee *big.Int) *txSorter {
235-
return &txSorter{
236-
txs: txs,
237-
baseFee: baseFee,
238-
}
239-
}
240-
241-
func (s *txSorter) Len() int { return len(s.txs) }
242-
func (s *txSorter) Swap(i, j int) {
243-
s.txs[i], s.txs[j] = s.txs[j], s.txs[i]
244-
}
245-
func (s *txSorter) Less(i, j int) bool {
246-
// It's okay to discard the error because a tx would never be
247-
// accepted into a block with an invalid effective tip.
248-
tip1, _ := s.txs[i].EffectiveGasTip(s.baseFee)
249-
tip2, _ := s.txs[j].EffectiveGasTip(s.baseFee)
250-
return tip1.Cmp(tip2) < 0
251-
}
252-
253-
// getBlockPrices calculates the lowest transaction gas price in a given block
241+
// getBlockValues calculates the lowest transaction gas price in a given block
254242
// and sends it to the result channel. If the block is empty or all transactions
255243
// are sent by the miner itself(it doesn't make any sense to include this kind of
256244
// transaction prices for sampling), nil gasprice is returned.
257-
func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
245+
func (oracle *Oracle) getBlockValues(ctx context.Context, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
258246
block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
259247
if block == nil {
260248
select {
@@ -263,15 +251,24 @@ func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, b
263251
}
264252
return
265253
}
254+
signer := types.MakeSigner(oracle.backend.ChainConfig(), block.Number())
255+
266256
// Sort the transaction by effective tip in ascending sort.
267-
txs := make([]*types.Transaction, len(block.Transactions()))
268-
copy(txs, block.Transactions())
269-
sorter := newSorter(txs, block.BaseFee())
270-
sort.Sort(sorter)
257+
txs := block.Transactions()
258+
sortedTxs := make([]*types.Transaction, len(txs))
259+
copy(sortedTxs, txs)
260+
baseFee := block.BaseFee()
261+
slices.SortFunc(sortedTxs, func(a, b *types.Transaction) int {
262+
// It's okay to discard the error because a tx would never be
263+
// accepted into a block with an invalid effective tip.
264+
tip1, _ := a.EffectiveGasTip(baseFee)
265+
tip2, _ := b.EffectiveGasTip(baseFee)
266+
return tip1.Cmp(tip2)
267+
})
271268

272269
var prices []*big.Int
273-
for _, tx := range sorter.txs {
274-
tip, _ := tx.EffectiveGasTip(block.BaseFee())
270+
for _, tx := range sortedTxs {
271+
tip, _ := tx.EffectiveGasTip(baseFee)
275272
if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 {
276273
continue
277274
}
@@ -288,9 +285,3 @@ func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, b
288285
case <-quit:
289286
}
290287
}
291-
292-
type bigIntArray []*big.Int
293-
294-
func (s bigIntArray) Len() int { return len(s) }
295-
func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
296-
func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

eth/gasprice/gasprice_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ func TestSuggestTipCap(t *testing.T) {
174174
config := Config{
175175
Blocks: 3,
176176
Percentile: 60,
177-
Default: big.NewInt(params.GWei),
178177
}
179178
var cases = []struct {
180179
fork *big.Int // Eip1559 fork number
@@ -188,7 +187,7 @@ func TestSuggestTipCap(t *testing.T) {
188187
}
189188
for _, c := range cases {
190189
backend := newTestBackend(t, c.fork, false)
191-
oracle := NewOracle(backend, config)
190+
oracle := NewOracle(backend, config, big.NewInt(params.GWei))
192191

193192
// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G
194193
got, err := oracle.SuggestTipCap(context.Background())

0 commit comments

Comments
 (0)