Skip to content

Commit 45b50c1

Browse files
authored
Merge pull request #492 from uprendis/feature/sharper-gasprice
Sharper gasprice estimation
2 parents 7c55b09 + 2830c82 commit 45b50c1

File tree

7 files changed

+105
-60
lines changed

7 files changed

+105
-60
lines changed

evmcore/tx_pool.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -556,17 +556,6 @@ func (pool *TxPool) Pending(enforceTips bool) (map[common.Address]types.Transact
556556
return pending, nil
557557
}
558558

559-
func (pool *TxPool) PendingSlice() types.Transactions {
560-
pool.mu.Lock()
561-
defer pool.mu.Unlock()
562-
563-
pending := make(types.Transactions, 0, 1000)
564-
for _, list := range pool.pending {
565-
pending = append(pending, list.Flatten()...)
566-
}
567-
return pending
568-
}
569-
570559
func (pool *TxPool) SampleHashes(max int) []common.Hash {
571560
return pool.all.SampleHashes(max)
572561
}

gossip/dummy_tx_pool.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,6 @@ 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-
8679
func (p *dummyTxPool) SubscribeNewTxsNotify(ch chan<- evmcore.NewTxsNotify) notify.Subscription {
8780
return p.txFeed.Subscribe(ch)
8881
}

gossip/gasprice/gasprice.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
"github.com/Fantom-foundation/lachesis-base/inter/idx"
2626
"github.com/Fantom-foundation/lachesis-base/utils/piecefunc"
27+
"github.com/ethereum/go-ethereum/common"
2728
"github.com/ethereum/go-ethereum/common/math"
2829
"github.com/ethereum/go-ethereum/core/types"
2930
lru "github.com/hashicorp/golang-lru"
@@ -56,7 +57,7 @@ type Reader interface {
5657
TotalGasPowerLeft() uint64
5758
GetRules() opera.Rules
5859
GetPendingRules() opera.Rules
59-
PendingTxs() types.Transactions
60+
PendingTxs() map[common.Address]types.Transactions
6061
}
6162

6263
type tipCache struct {

gossip/gasprice/gasprice_test.go

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"github.com/Fantom-foundation/lachesis-base/inter/idx"
8+
"github.com/ethereum/go-ethereum/common"
89
"github.com/ethereum/go-ethereum/common/math"
910
"github.com/ethereum/go-ethereum/core/types"
1011
"github.com/stretchr/testify/require"
@@ -42,14 +43,17 @@ func (t TestBackend) GetPendingRules() opera.Rules {
4243
return t.pendingRules
4344
}
4445

45-
func (t TestBackend) PendingTxs() types.Transactions {
46-
txs := make(types.Transactions, 0, len(t.pendingTxs))
47-
for _, tx := range t.pendingTxs {
48-
txs = append(txs, types.NewTx(&types.DynamicFeeTx{
49-
GasTipCap: tx.tip,
50-
GasFeeCap: tx.cap,
51-
Gas: tx.gas,
52-
}))
46+
func (t TestBackend) PendingTxs() map[common.Address]types.Transactions {
47+
txs := make(map[common.Address]types.Transactions, len(t.pendingTxs))
48+
for i, tx := range t.pendingTxs {
49+
txs[common.BytesToAddress(big.NewInt(int64(i)).Bytes())] = types.Transactions{
50+
types.NewTx(&types.DynamicFeeTx{
51+
Nonce: uint64(i),
52+
GasTipCap: tx.tip,
53+
GasFeeCap: tx.cap,
54+
Gas: tx.gas,
55+
}),
56+
}
5357
}
5458
return txs
5559
}
@@ -243,7 +247,7 @@ func TestOracle_reactiveGasPrice(t *testing.T) {
243247
for i := 0; i < statsBuffer-5; i++ {
244248
gpo.txpoolStatsTick()
245249
}
246-
require.Equal(t, "958333333", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
250+
require.Equal(t, "933333333", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
247251
require.Equal(t, "3500000000", gpo.reactiveGasPrice(DecimalUnit).String())
248252
gpo.txpoolStatsTick()
249253
require.Equal(t, "1000000000", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
@@ -255,16 +259,23 @@ func TestOracle_reactiveGasPrice(t *testing.T) {
255259
// change minGasPrice
256260
backend.rules.Economy.MinGasPrice = big.NewInt(100)
257261
gpo.txpoolStatsTick()
258-
require.Equal(t, "958333337", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
259-
require.Equal(t, "3479166670", gpo.reactiveGasPrice(DecimalUnit).String())
262+
require.Equal(t, "933333340", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
263+
require.Equal(t, "3466666673", gpo.reactiveGasPrice(DecimalUnit).String())
260264
gpo.txpoolStatsTick()
261-
require.Equal(t, "916666675", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
262-
require.Equal(t, "3458333341", gpo.reactiveGasPrice(DecimalUnit).String())
263-
for i := 0; i < statsBuffer-3; i++ {
265+
require.Equal(t, "866666680", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
266+
require.Equal(t, "3433333346", gpo.reactiveGasPrice(DecimalUnit).String())
267+
gpo.txpoolStatsTick()
268+
require.Equal(t, "800000020", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
269+
require.Equal(t, "3400000020", gpo.reactiveGasPrice(DecimalUnit).String())
270+
gpo.txpoolStatsTick()
271+
// recent gas price plus 5%
272+
require.Equal(t, "105", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
273+
require.Equal(t, "3150000105", gpo.reactiveGasPrice(DecimalUnit).String())
274+
for i := 0; i < statsBuffer-5; i++ {
264275
gpo.txpoolStatsTick()
265276
}
266-
require.Equal(t, "41666762", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
267-
require.Equal(t, "3020833429", gpo.reactiveGasPrice(DecimalUnit).String())
277+
require.Equal(t, "105", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
278+
require.Equal(t, "3033333426", gpo.reactiveGasPrice(DecimalUnit).String())
268279
gpo.txpoolStatsTick()
269280
require.Equal(t, "100", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
270281
require.Equal(t, "3000000100", gpo.reactiveGasPrice(DecimalUnit).String())
@@ -275,15 +286,15 @@ func TestOracle_reactiveGasPrice(t *testing.T) {
275286
// half of txs are confirmed now
276287
backend.pendingTxs = backend.pendingTxs[:2]
277288
gpo.txpoolStatsTick()
278-
require.Equal(t, "95", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
289+
require.Equal(t, "93", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
279290
require.Equal(t, "3000000100", gpo.reactiveGasPrice(DecimalUnit).String())
280291
gpo.txpoolStatsTick()
281-
require.Equal(t, "91", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
292+
require.Equal(t, "86", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
282293
require.Equal(t, "3000000100", gpo.reactiveGasPrice(DecimalUnit).String())
283294
for i := 0; i < statsBuffer-3; i++ {
284295
gpo.txpoolStatsTick()
285296
}
286-
require.Equal(t, "4", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
297+
require.Equal(t, "0", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
287298
require.Equal(t, "3000000100", gpo.reactiveGasPrice(DecimalUnit).String())
288299
gpo.txpoolStatsTick()
289300
require.Equal(t, "0", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
@@ -304,7 +315,7 @@ func TestOracle_reactiveGasPrice(t *testing.T) {
304315
gpo.txpoolStatsTick()
305316
}
306317
require.Equal(t, "0", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
307-
require.Equal(t, "3000000100", gpo.reactiveGasPrice(DecimalUnit).String())
318+
require.Equal(t, "0", gpo.reactiveGasPrice(DecimalUnit).String())
308319
gpo.txpoolStatsTick()
309320
require.Equal(t, "0", gpo.reactiveGasPrice(0.8*DecimalUnit).String())
310321
require.Equal(t, "0", gpo.reactiveGasPrice(DecimalUnit).String())

gossip/gasprice/reactive.go

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
const (
1414
percentilesPerStat = 20
1515
statUpdatePeriod = 1 * time.Second
16-
statsBuffer = int((24 * time.Second) / statUpdatePeriod)
16+
statsBuffer = int((15 * time.Second) / statUpdatePeriod)
1717
maxGasToIndex = 40000000
1818
)
1919

@@ -79,6 +79,13 @@ func (gpo *Oracle) txpoolStatsLoop() {
7979
}
8080
}
8181

82+
func (c *circularTxpoolStats) dec(v int) int {
83+
if v == 0 {
84+
return len(c.stats) - 1
85+
}
86+
return v - 1
87+
}
88+
8289
// calcAvg calculates average of statistics in the circular buffer
8390
func (c *circularTxpoolStats) calcAvg() txpoolStat {
8491
avg := txpoolStat{}
@@ -93,9 +100,10 @@ func (c *circularTxpoolStats) calcAvg() txpoolStat {
93100
nonZero++
94101
avg.totalGas += s.totalGas
95102
for p := range s.percentiles {
96-
if s.percentiles[p] != nil {
97-
avg.percentiles[p].Add(avg.percentiles[p], s.percentiles[p])
103+
if s.percentiles[p] == nil {
104+
continue
98105
}
106+
avg.percentiles[p].Add(avg.percentiles[p], s.percentiles[p])
99107
}
100108
}
101109
if nonZero == 0 {
@@ -106,7 +114,44 @@ func (c *circularTxpoolStats) calcAvg() txpoolStat {
106114
for p := range avg.percentiles {
107115
avg.percentiles[p].Div(avg.percentiles[p], nonZeroBn)
108116
}
109-
return avg
117+
118+
// take maximum from previous 4 stats plus 5%
119+
rec := txpoolStat{}
120+
for p := range rec.percentiles {
121+
rec.percentiles[p] = new(big.Int)
122+
}
123+
recI1 := c.dec(c.i)
124+
recI2 := c.dec(recI1)
125+
recI3 := c.dec(recI2)
126+
recI4 := c.dec(recI3)
127+
for _, s := range []txpoolStat{c.stats[recI1], c.stats[recI2], c.stats[recI3], c.stats[recI4]} {
128+
for p := range s.percentiles {
129+
if s.percentiles[p] == nil {
130+
continue
131+
}
132+
if rec.percentiles[p].Cmp(s.percentiles[p]) < 0 {
133+
rec.percentiles[p].Set(s.percentiles[p])
134+
}
135+
}
136+
}
137+
// increase by 5%
138+
for p := range rec.percentiles {
139+
rec.percentiles[p].Mul(rec.percentiles[p], big.NewInt(21))
140+
rec.percentiles[p].Div(rec.percentiles[p], big.NewInt(20))
141+
}
142+
143+
// return minimum from max(recent two stats * 1.05) and avg stats
144+
res := txpoolStat{}
145+
res.totalGas = avg.totalGas
146+
for _, s := range []txpoolStat{avg, rec} {
147+
for p := range s.percentiles {
148+
if res.percentiles[p] == nil || res.percentiles[p].Cmp(s.percentiles[p]) > 0 {
149+
res.percentiles[p] = s.percentiles[p]
150+
}
151+
}
152+
}
153+
154+
return res
110155
}
111156

112157
func (c *circularTxpoolStats) getGasPriceForGasAbove(gas uint64) *big.Int {
@@ -161,12 +206,18 @@ func (c *circularTxpoolStats) totalGas() uint64 {
161206

162207
// calcTxpoolStat retrieves txpool transactions and calculates statistics
163208
func (gpo *Oracle) calcTxpoolStat() txpoolStat {
164-
txs := gpo.backend.PendingTxs()
209+
txsMap := gpo.backend.PendingTxs()
165210
s := txpoolStat{}
166-
if len(txs) == 0 {
211+
if len(txsMap) == 0 {
167212
// short circuit if empty txpool
168213
return s
169214
}
215+
// take only one tx from each account
216+
txs := make(types.Transactions, 0, 1000)
217+
for _, aTxs := range txsMap {
218+
txs = append(txs, aTxs[0])
219+
}
220+
170221
// don't index more transactions than needed for GPO purposes
171222
const maxTxsToIndex = 400
172223

@@ -175,28 +226,24 @@ func (gpo *Oracle) calcTxpoolStat() txpoolStat {
175226
sorted := txs
176227
sort.Slice(sorted, func(i, j int) bool {
177228
a, b := sorted[i], sorted[j]
178-
return a.EffectiveGasTipCmp(b, minGasPrice) < 0
229+
cmp := a.EffectiveGasTipCmp(b, minGasPrice)
230+
if cmp == 0 {
231+
return a.Gas() > b.Gas()
232+
}
233+
return cmp > 0
179234
})
180235

181-
if len(txs) > maxTxsToIndex {
182-
txs = txs[:maxTxsToIndex]
183-
}
184-
sortedDown := make(types.Transactions, len(sorted))
185236
for i, tx := range sorted {
186-
sortedDown[len(sorted)-1-i] = tx
187-
}
188-
189-
for i, tx := range sortedDown {
190237
s.totalGas += tx.Gas()
191-
if s.totalGas > maxGasToIndex {
192-
sortedDown = sortedDown[:i+1]
238+
if s.totalGas > maxGasToIndex || i > maxTxsToIndex {
239+
sorted = sorted[:i+1]
193240
break
194241
}
195242
}
196243

197244
gasCounter := uint64(0)
198245
p := uint64(0)
199-
for _, tx := range sortedDown {
246+
for _, tx := range sorted {
200247
for p < uint64(len(s.percentiles)) && gasCounter >= p*maxGasToIndex/uint64(len(s.percentiles)) {
201248
s.percentiles[p] = tx.EffectiveGasTipValue(minGasPrice)
202249
if s.percentiles[p].Sign() < 0 {

gossip/gpo_backend.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package gossip
33
import (
44
"github.com/Fantom-foundation/lachesis-base/hash"
55
"github.com/Fantom-foundation/lachesis-base/inter/idx"
6+
"github.com/ethereum/go-ethereum/common"
67
"github.com/ethereum/go-ethereum/core/types"
78

89
"github.com/Fantom-foundation/go-opera/eventcheck/gaspowercheck"
@@ -32,8 +33,12 @@ func (b *GPOBackend) GetPendingRules() opera.Rules {
3233
return es.Rules
3334
}
3435

35-
func (b *GPOBackend) PendingTxs() types.Transactions {
36-
return b.txpool.PendingSlice()
36+
func (b *GPOBackend) PendingTxs() map[common.Address]types.Transactions {
37+
txs, err := b.txpool.Pending(false)
38+
if err != nil {
39+
return map[common.Address]types.Transactions{}
40+
}
41+
return txs
3742
}
3843

3944
// TotalGasPowerLeft returns a total amount of obtained gas power by the validators, according to the latest events from each validator

gossip/protocol.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ type TxPool interface {
119119
Stats() (int, int)
120120
Content() (map[common.Address]types.Transactions, map[common.Address]types.Transactions)
121121
ContentFrom(addr common.Address) (types.Transactions, types.Transactions)
122-
PendingSlice() types.Transactions
123122
}
124123

125124
// handshakeData is the network packet for the initial handshake message

0 commit comments

Comments
 (0)