Skip to content

Commit

Permalink
Refactor of the multi currency price heap (celo-org#1839)
Browse files Browse the repository at this point in the history
  • Loading branch information
hbandura authored Feb 17, 2022
1 parent 638d2f4 commit b77c485
Show file tree
Hide file tree
Showing 4 changed files with 492 additions and 128 deletions.
35 changes: 29 additions & 6 deletions core/sys_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type SysContractCallCtx struct {
gasForAlternativeCurrency uint64
// gasPriceMinimums stores values for whitelisted currencies keyed by their contract address
// Note that native token(CELO) is keyed by common.ZeroAddress
gasPriceMinimums map[common.Address]*big.Int
gasPriceMinimums GasPriceMinimums
}

// NewSysContractCallCtx creates the SysContractCallCtx object and makes the contract calls.
Expand Down Expand Up @@ -63,7 +63,34 @@ func (sc *SysContractCallCtx) IsWhitelisted(feeCurrency *common.Address) bool {
}

// GetGasPriceMinimum retrieves gas price minimum for given fee currency address.
// Note that the CELO currency is keyed by the Zero address.
func (sc *SysContractCallCtx) GetGasPriceMinimum(feeCurrency *common.Address) *big.Int {
return sc.gasPriceMinimums.GetGasPriceMinimum(feeCurrency)
}

// GetCurrentGasPriceMinimumMap returns the gas price minimum map for all whitelisted currencies.
// Note that the CELO currency is keyed by the Zero address.
func (sc *SysContractCallCtx) GetCurrentGasPriceMinimumMap() GasPriceMinimums {
return sc.gasPriceMinimums
}

type GasPriceMinimums map[common.Address]*big.Int

func (gpm GasPriceMinimums) valOrDefault(key common.Address) *big.Int {
val, ok := gpm[key]
if !ok {
return gasprice_minimum.FallbackGasPriceMinimum
}
return val
}

// GetNativeGPM retrieves the gas price minimum for the native currency.
func (gpm GasPriceMinimums) GetNativeGPM() *big.Int {
return gpm.valOrDefault(common.ZeroAddress)
}

// GetGasPriceMinimum retrieves gas price minimum for given fee currency address, it returns gasprice_minimum.FallbackGasPriceMinimum when there is an error
func (gpm GasPriceMinimums) GetGasPriceMinimum(feeCurrency *common.Address) *big.Int {
// feeCurrency for native token(CELO) is nil, so we bind common.ZeroAddress as key
var key common.Address
if feeCurrency == nil {
Expand All @@ -72,9 +99,5 @@ func (sc *SysContractCallCtx) GetGasPriceMinimum(feeCurrency *common.Address) *b
key = *feeCurrency
}

gasPriceMinimum, ok := sc.gasPriceMinimums[key]
if !ok {
return gasprice_minimum.FallbackGasPriceMinimum
}
return gasPriceMinimum
return gpm.valOrDefault(key)
}
127 changes: 5 additions & 122 deletions core/tx_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -612,113 +612,6 @@ func (h *priceHeap) Pop() interface{} {
return x
}

// multiCurrencyPriceHeap is a heap.Interface implementation over transactions
// with different fee currencies for retrieving price-sorted transactions to discard
// when the pool fills up. If baseFee is set then the heap is sorted based on the
// effective tip based on the given base fee. If baseFee is nil then the sorting
// is based on gasFeeCap.
type multiCurrencyPriceHeap struct {
currencyCmpFn func(*big.Int, *common.Address, *big.Int, *common.Address) int
baseFeeFn func(*common.Address) *big.Int // heap should always be re-sorted after baseFee is changed
nonNilCurrencyHeaps map[common.Address]*priceHeap // Heap of prices of all the stored non-nil currency transactions
nilCurrencyHeap *priceHeap // Heap of prices of all the stored nil currency transactions

}

// Add to the heap. Must call Init afterwards to retain the heap invariants.
func (h *multiCurrencyPriceHeap) Add(tx *types.Transaction) {
if fc := tx.FeeCurrency(); fc == nil {
h.nilCurrencyHeap.list = append(h.nilCurrencyHeap.list, tx)
} else {
if _, ok := h.nonNilCurrencyHeaps[*fc]; !ok {
h.nonNilCurrencyHeaps[*fc] = &priceHeap{
baseFee: h.baseFeeFn(fc),
}

}
sh := h.nonNilCurrencyHeaps[*fc]
sh.list = append(sh.list, tx)
}
}

func (h *multiCurrencyPriceHeap) Push(tx *types.Transaction) {
if fc := tx.FeeCurrency(); fc == nil {
h.nilCurrencyHeap.Push(tx)
} else {
if _, ok := h.nonNilCurrencyHeaps[*fc]; !ok {
h.nonNilCurrencyHeaps[*fc] = &priceHeap{
baseFee: h.baseFeeFn(fc),
}

}
sh := h.nonNilCurrencyHeaps[*fc]
sh.Push(tx)
}
}

func (h *multiCurrencyPriceHeap) Pop() *types.Transaction {
var cheapestHeap *priceHeap
var cheapestTxn *types.Transaction

if len(h.nilCurrencyHeap.list) > 0 {
cheapestHeap = h.nilCurrencyHeap
cheapestTxn = h.nilCurrencyHeap.list[0]
}

for _, priceHeap := range h.nonNilCurrencyHeaps {
if len(priceHeap.list) > 0 {
if cheapestHeap == nil {
cheapestHeap = priceHeap
cheapestTxn = cheapestHeap.list[0]
} else {
txn := priceHeap.list[0]
if h.currencyCmpFn(txn.GasPrice(), txn.FeeCurrency(), cheapestTxn.GasPrice(), cheapestTxn.FeeCurrency()) < 0 {
cheapestHeap = priceHeap
cheapestTxn = txn
}
}
}
}

if cheapestHeap != nil {
return heap.Pop(cheapestHeap).(*types.Transaction)
}
return nil

}

func (h *multiCurrencyPriceHeap) Len() int {
r := len(h.nilCurrencyHeap.list)
for _, priceHeap := range h.nonNilCurrencyHeaps {
r += len(priceHeap.list)
}
return r
}

func (h *multiCurrencyPriceHeap) Init() {
heap.Init(h.nilCurrencyHeap)
for _, priceHeap := range h.nonNilCurrencyHeaps {
heap.Init(priceHeap)
}
}

func (h *multiCurrencyPriceHeap) Clear() {
h.nilCurrencyHeap.list = nil
for _, priceHeap := range h.nonNilCurrencyHeaps {
priceHeap.list = nil
}
}

func (h *multiCurrencyPriceHeap) SetBaseFee(txCtx *txPoolContext) {
h.currencyCmpFn = txCtx.CmpValues
h.baseFeeFn = txCtx.GetGasPriceMinimum
h.nilCurrencyHeap.baseFee = txCtx.GetGasPriceMinimum(nil)
for currencyAddr, heap := range h.nonNilCurrencyHeaps {
heap.baseFee = txCtx.GetGasPriceMinimum(&currencyAddr)
}

}

// txPricedList is a price-sorted heap to allow operating on transactions pool
// contents in a price-incrementing way. It's built opon the all transactions
// in txpool but only interested in the remote part. It means only remote transactions
Expand Down Expand Up @@ -752,18 +645,8 @@ func newTxPricedList(all *txLookup, ctx *atomic.Value, maxStales int64) *txPrice
ctx: ctx,
all: all,
maxStales: maxStales,
urgent: multiCurrencyPriceHeap{
currencyCmpFn: txCtx.CmpValues,
nilCurrencyHeap: &priceHeap{},
nonNilCurrencyHeaps: make(map[common.Address]*priceHeap),
baseFeeFn: txCtx.GetGasPriceMinimum,
},
floating: multiCurrencyPriceHeap{
currencyCmpFn: txCtx.CmpValues,
nilCurrencyHeap: &priceHeap{},
nonNilCurrencyHeaps: make(map[common.Address]*priceHeap),
baseFeeFn: txCtx.GetGasPriceMinimum,
},
urgent: newMultiCurrencyPriceHeap(txCtx.CmpValues, txCtx.SysContractCallCtx.GetCurrentGasPriceMinimumMap()),
floating: newMultiCurrencyPriceHeap(txCtx.CmpValues, txCtx.SysContractCallCtx.GetCurrentGasPriceMinimumMap()),
}
}

Expand Down Expand Up @@ -808,8 +691,8 @@ func (l *txPricedList) Underpriced(tx *types.Transaction) bool {
}

func (l *txPricedList) underpricedForMulti(h *multiCurrencyPriceHeap, tx *types.Transaction) bool {
underpriced := l.underpricedFor(h.nilCurrencyHeap, tx)
for _, sh := range h.nonNilCurrencyHeaps {
underpriced := l.underpricedFor(h.nativeCurrencyHeap, tx)
for _, sh := range h.currencyHeaps {
if l.underpricedFor(sh, tx) {
underpriced = true
}
Expand Down Expand Up @@ -911,6 +794,6 @@ func (l *txPricedList) Reheap() {
// SetBaseFee updates the base fee and triggers a re-heap. Note that Removed is not
// necessary to call right before SetBaseFee when processing a new block.
func (l *txPricedList) SetBaseFee(txCtx *txPoolContext) {
l.urgent.SetBaseFee(txCtx)
l.urgent.UpdateFeesAndCurrencies(txCtx.CmpValues, txCtx.SysContractCallCtx.GetCurrentGasPriceMinimumMap())
l.Reheap()
}
132 changes: 132 additions & 0 deletions core/tx_multicurrency_priceheap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package core

import (
"container/heap"
"math/big"

"github.com/celo-org/celo-blockchain/common"
"github.com/celo-org/celo-blockchain/core/types"
)

type CurrencyCmpFn func(*big.Int, *common.Address, *big.Int, *common.Address) int

// IsCheaper returns true if tx1 is cheaper than tx2 (GasPrice with currency comparison)
func (cc CurrencyCmpFn) IsCheaper(tx1, tx2 *types.Transaction) bool {
return cc(tx1.GasPrice(), tx1.FeeCurrency(), tx2.GasPrice(), tx2.FeeCurrency()) < 0
}

// multiCurrencyPriceHeap is a heap.Interface implementation over transactions
// with different fee currencies for retrieving price-sorted transactions to discard
// when the pool fills up. If baseFee is set then the heap is sorted based on the
// effective tip based on the given base fee. If baseFee is nil then the sorting
// is based on gasFeeCap.
type multiCurrencyPriceHeap struct {
currencyCmp CurrencyCmpFn
gpm GasPriceMinimums // heap should always be re-sorted after gas price minimums (baseFees) is changed
currencyHeaps map[common.Address]*priceHeap // Heap of prices of all the stored non-nil currency transactions
nativeCurrencyHeap *priceHeap // Heap of prices of all the stored nil currency transactions
}

func newMultiCurrencyPriceHeap(currencyCmp CurrencyCmpFn, gpm GasPriceMinimums) multiCurrencyPriceHeap {
return multiCurrencyPriceHeap{
currencyCmp: currencyCmp,
gpm: gpm,

// inner state

nativeCurrencyHeap: &priceHeap{},
currencyHeaps: make(map[common.Address]*priceHeap),
}
}

// getHeapFor returns the proper heap for the given transaction, and creates it
// if it's not available in the currencyHeaps
func (h *multiCurrencyPriceHeap) getHeapFor(tx *types.Transaction) *priceHeap {
fc := tx.FeeCurrency()
if fc == nil {
return h.nativeCurrencyHeap
}
if _, ok := h.currencyHeaps[*fc]; !ok {
h.currencyHeaps[*fc] = &priceHeap{
baseFee: h.gpm.GetGasPriceMinimum(fc),
}
}
return h.currencyHeaps[*fc]
}

// Add to the heap. Must call Init afterwards to retain the heap invariants.
func (h *multiCurrencyPriceHeap) Add(tx *types.Transaction) {
ph := h.getHeapFor(tx)
ph.list = append(ph.list, tx)
}

// Push to the heap, maintains heap invariants.
func (h *multiCurrencyPriceHeap) Push(tx *types.Transaction) {
ph := h.getHeapFor(tx)
heap.Push(ph, tx)
}

func (h *multiCurrencyPriceHeap) cheapestTxs() []*types.Transaction {
txs := make([]*types.Transaction, 0, 1+len(h.currencyHeaps))
if len(h.nativeCurrencyHeap.list) > 0 {
txs = append(txs, h.nativeCurrencyHeap.list[0])
}
for _, ph := range h.currencyHeaps {
if len(ph.list) > 0 {
txs = append(txs, ph.list[0])
}
}
return txs
}

func (h *multiCurrencyPriceHeap) cheapestTx() *types.Transaction {
txs := h.cheapestTxs()
var cheapestTx *types.Transaction
for _, tx := range txs {
if cheapestTx == nil || h.currencyCmp.IsCheaper(tx, cheapestTx) {
cheapestTx = tx
}
}
return cheapestTx
}

func (h *multiCurrencyPriceHeap) Pop() *types.Transaction {
cheapestTx := h.cheapestTx()
if cheapestTx == nil {
return nil
}
ph := h.getHeapFor(cheapestTx)
return heap.Pop(ph).(*types.Transaction)
}

func (h *multiCurrencyPriceHeap) Len() int {
r := len(h.nativeCurrencyHeap.list)
for _, priceHeap := range h.currencyHeaps {
r += len(priceHeap.list)
}
return r
}

func (h *multiCurrencyPriceHeap) Init() {
heap.Init(h.nativeCurrencyHeap)
for _, priceHeap := range h.currencyHeaps {
heap.Init(priceHeap)
}
}

func (h *multiCurrencyPriceHeap) Clear() {
h.nativeCurrencyHeap.list = nil
for _, priceHeap := range h.currencyHeaps {
priceHeap.list = nil
}
}

func (h *multiCurrencyPriceHeap) UpdateFeesAndCurrencies(currencyCmpFn CurrencyCmpFn, gpm GasPriceMinimums) {
h.currencyCmp = currencyCmpFn
h.gpm = gpm
h.nativeCurrencyHeap.baseFee = gpm.GetNativeGPM()
for currencyAddr, heap := range h.currencyHeaps {
heap.baseFee = gpm.GetGasPriceMinimum(&currencyAddr)
}

}
Loading

0 comments on commit b77c485

Please sign in to comment.