Skip to content

Commit

Permalink
EVM-728 Gas price comparison for executable transactions in `pricedQu…
Browse files Browse the repository at this point in the history
…eue` (#1696)
  • Loading branch information
igorcrevar committed Jul 11, 2023
1 parent 8565d45 commit f4efe97
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 159 deletions.
3 changes: 2 additions & 1 deletion gasprice/feehistory.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,13 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc
}

sorter := make([]*txGasAndReward, len(block.Transactions))
baseFee := new(big.Int).SetUint64(block.Header.BaseFee)

for j, tx := range block.Transactions {
cost := tx.Cost()
sorter[j] = &txGasAndReward{
gasUsed: cost.Sub(cost, tx.Value),
reward: tx.EffectiveTip(block.Header.BaseFee),
reward: tx.EffectiveGasTip(baseFee),
}
}

Expand Down
12 changes: 6 additions & 6 deletions gasprice/gasprice.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func (g *GasHelper) MaxPriorityFeePerGas() (*big.Int, error) {
var allPrices []*big.Int

collectPrices := func(block *types.Block) error {
baseFee := block.Header.BaseFee
baseFee := new(big.Int).SetUint64(block.Header.BaseFee)
txSorter := newTxByEffectiveTipSorter(block.Transactions, baseFee)
sort.Sort(txSorter)

Expand All @@ -150,7 +150,7 @@ func (g *GasHelper) MaxPriorityFeePerGas() (*big.Int, error) {
blockTxPrices := make([]*big.Int, 0)

for _, tx := range txSorter.txs {
tip := tx.EffectiveTip(baseFee)
tip := tx.EffectiveGasTip(baseFee)

if tip.Cmp(g.ignorePrice) == -1 {
// ignore transactions with tip lower than ignore price
Expand Down Expand Up @@ -238,11 +238,11 @@ func (g *GasHelper) MaxPriorityFeePerGas() (*big.Int, error) {
// txSortedByEffectiveTip sorts transactions by effective tip from smallest to largest
type txSortedByEffectiveTip struct {
txs []*types.Transaction
baseFee uint64
baseFee *big.Int
}

// newTxByEffectiveTipSorter is constructor function for txSortedByEffectiveTip
func newTxByEffectiveTipSorter(txs []*types.Transaction, baseFee uint64) *txSortedByEffectiveTip {
func newTxByEffectiveTipSorter(txs []*types.Transaction, baseFee *big.Int) *txSortedByEffectiveTip {
return &txSortedByEffectiveTip{
txs: txs,
baseFee: baseFee,
Expand All @@ -259,8 +259,8 @@ func (t *txSortedByEffectiveTip) Swap(i, j int) {

// Less is implementation of sort.Interface
func (t *txSortedByEffectiveTip) Less(i, j int) bool {
tip1 := t.txs[i].EffectiveTip(t.baseFee)
tip2 := t.txs[j].EffectiveTip(t.baseFee)
tip1 := t.txs[i].EffectiveGasTip(t.baseFee)
tip2 := t.txs[j].EffectiveGasTip(t.baseFee)

return tip1.Cmp(tip2) < 0
}
86 changes: 30 additions & 56 deletions txpool/queue_priced.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package txpool
import (
"container/heap"
"math/big"
"sync/atomic"

"github.com/0xPolygon/polygon-edge/types"
)
Expand All @@ -12,19 +11,18 @@ type pricedQueue struct {
queue *maxPriceQueue
}

func newPricedQueue() *pricedQueue {
q := pricedQueue{
queue: &maxPriceQueue{},
// newPricesQueue creates the priced queue with initial transactions and base fee
func newPricesQueue(baseFee uint64, initialTxs []*types.Transaction) *pricedQueue {
q := &pricedQueue{
queue: &maxPriceQueue{
baseFee: new(big.Int).SetUint64(baseFee),
txs: initialTxs,
},
}

heap.Init(q.queue)

return &q
}

// clear empties the underlying queue.
func (q *pricedQueue) clear() {
q.queue.txs = q.queue.txs[:0]
return q
}

// Pushes the given transactions onto the queue.
Expand All @@ -48,13 +46,13 @@ func (q *pricedQueue) pop() *types.Transaction {
}

// length returns the number of transactions in the queue.
func (q *pricedQueue) length() uint64 {
return uint64(q.queue.Len())
func (q *pricedQueue) length() int {
return q.queue.Len()
}

// transactions sorted by gas price (descending)
type maxPriceQueue struct {
baseFee uint64
baseFee *big.Int
txs []*types.Transaction
}

Expand All @@ -76,17 +74,6 @@ func (q *maxPriceQueue) Swap(i, j int) {
q.txs[i], q.txs[j] = q.txs[j], q.txs[i]
}

func (q *maxPriceQueue) Less(i, j int) bool {
switch q.cmp(q.txs[i], q.txs[j]) {
case -1:
return true
case 1:
return false
default:
return q.txs[i].Nonce > q.txs[j].Nonce
}
}

func (q *maxPriceQueue) Push(x interface{}) {
transaction, ok := x.(*types.Transaction)
if !ok {
Expand All @@ -105,45 +92,32 @@ func (q *maxPriceQueue) Pop() interface{} {
return x
}

// cmp compares the given transactions by their fees and returns:
// - 0 if they have same fees
// - 1 if a has higher fees than b
// - -1 if b has higher fees than a
func (q *maxPriceQueue) cmp(a, b *types.Transaction) int {
baseFee := atomic.LoadUint64(&q.baseFee)
effectiveTipA := a.EffectiveTip(baseFee)
effectiveTipB := b.EffectiveTip(baseFee)

// Compare effective tips if baseFee is specified
if c := effectiveTipA.Cmp(effectiveTipB); c != 0 {
return c
}

aGasFeeCap, bGasFeeCap := new(big.Int), new(big.Int)

if a.GasFeeCap != nil {
aGasFeeCap = aGasFeeCap.Set(a.GasFeeCap)
// @see https://github.com/etclabscore/core-geth/blob/4e2b0e37f89515a4e7b6bafaa40910a296cb38c0/core/txpool/list.go#L458
// for details why is something implemented like it is
func (q *maxPriceQueue) Less(i, j int) bool {
switch cmp(q.txs[i], q.txs[j], q.baseFee) {
case -1:
return false
case 1:
return true
default:
return q.txs[i].Nonce < q.txs[j].Nonce
}
}

if b.GasFeeCap != nil {
bGasFeeCap = bGasFeeCap.Set(b.GasFeeCap)
func cmp(a, b *types.Transaction, baseFee *big.Int) int {
if baseFee.BitLen() > 0 {
// Compare effective tips if baseFee is specified
if c := a.EffectiveGasTip(baseFee).Cmp(b.EffectiveGasTip(baseFee)); c != 0 {
return c
}
}

// Compare fee caps if baseFee is not specified or effective tips are equal
if c := aGasFeeCap.Cmp(bGasFeeCap); c != 0 {
if c := a.GetGasFeeCap().Cmp(b.GetGasFeeCap()); c != 0 {
return c
}

aGasTipCap, bGasTipCap := new(big.Int), new(big.Int)

if a.GasTipCap != nil {
aGasTipCap = aGasTipCap.Set(a.GasTipCap)
}

if b.GasTipCap != nil {
bGasTipCap = bGasTipCap.Set(b.GasTipCap)
}

// Compare tips if effective tips and fee caps are equal
return aGasTipCap.Cmp(bGasTipCap)
return a.GetGasTipCap().Cmp(b.GetGasTipCap())
}
26 changes: 10 additions & 16 deletions txpool/queue_priced_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ package txpool
import (
"math/big"
"math/rand"
"sort"
"testing"

"github.com/stretchr/testify/assert"

"github.com/0xPolygon/polygon-edge/types"
"github.com/stretchr/testify/assert"
)

func Test_maxPriceQueue(t *testing.T) {
Expand Down Expand Up @@ -224,6 +222,12 @@ func Test_maxPriceQueue(t *testing.T) {
},
},
},
{
name: "empty",
baseFee: 0,
unsorted: nil,
sorted: []*types.Transaction{},
},
}

for _, tt := range testTable {
Expand All @@ -232,15 +236,10 @@ func Test_maxPriceQueue(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

queue := &maxPriceQueue{
baseFee: tt.baseFee,
txs: tt.unsorted,
}

sort.Sort(queue)
queue := newPricesQueue(tt.baseFee, tt.unsorted)

for _, tx := range tt.sorted {
actual := queue.Pop()
actual := queue.pop()
assert.Equal(t, tx, actual)
}
})
Expand Down Expand Up @@ -269,12 +268,7 @@ func Benchmark_pricedQueue(t *testing.B) {
for _, tt := range testTable {
t.Run(tt.name, func(b *testing.B) {
for i := 0; i < t.N; i++ {
q := newPricedQueue()
q.queue.baseFee = uint64(i)

for _, tx := range tt.unsortedTxs {
q.push(tx)
}
q := newPricesQueue(uint64(100), tt.unsortedTxs)

for q.length() > 0 {
_ = q.pop()
Expand Down
21 changes: 4 additions & 17 deletions txpool/txpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ func NewTxPool(
logger: logger.Named("txpool"),
forks: forks,
store: store,
executables: newPricedQueue(),
executables: newPricesQueue(0, nil),
accounts: accountsMap{maxEnqueuedLimit: config.MaxAccountEnqueued},
index: lookupMap{all: make(map[types.Hash]*types.Transaction)},
gauge: slotGauge{height: 0, max: config.MaxSlots},
Expand Down Expand Up @@ -325,21 +325,14 @@ func (p *TxPool) AddTx(tx *types.Transaction) error {
// Prepare generates all the transactions
// ready for execution. (primaries)
func (p *TxPool) Prepare(baseFee uint64) {
// clear from previous round
if p.executables.length() != 0 {
p.executables.clear()
}

// set base fee
p.updateBaseFee(baseFee)
atomic.StoreUint64(&p.baseFee, baseFee)

// fetch primary from each account
primaries := p.accounts.getPrimaries()

// push primaries to the executables queue
for _, tx := range primaries {
p.executables.push(tx)
}
// create new executables queue with base fee and initial transactions (primaries)
p.executables = newPricesQueue(baseFee, primaries)
}

// Peek returns the best-price selected
Expand Down Expand Up @@ -1027,12 +1020,6 @@ func (p *TxPool) Length() uint64 {
return p.accounts.promoted()
}

// updateBaseFee updates base fee in the tx pool and priced queue
func (p *TxPool) updateBaseFee(baseFee uint64) {
atomic.StoreUint64(&p.baseFee, baseFee)
atomic.StoreUint64(&p.executables.queue.baseFee, baseFee)
}

// toHash returns the hash(es) of given transaction(s)
func toHash(txs ...*types.Transaction) (hashes []types.Hash) {
for _, tx := range txs {
Expand Down
Loading

0 comments on commit f4efe97

Please sign in to comment.