Skip to content

Commit

Permalink
Port the block limit per fee currency (#119)
Browse files Browse the repository at this point in the history
* Port the block limit per fee currency feature

Closes #65

This implements the block-limit per fee-currency feature.
Some parts of this have been directly ported from celo-blockchain
(celo-org/celo-blockchain@dc45bdc00).

* Copy MultiGasPool in miner environment

* Add USDT and USDC to block-limit per currency defaults

* Initialize MultiGasPool with currency whitelist
  • Loading branch information
ezdac authored and carterqw2 committed Jun 11, 2024
1 parent ff58997 commit 7bac150
Show file tree
Hide file tree
Showing 12 changed files with 383 additions and 21 deletions.
2 changes: 2 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ var (
utils.MinerExtraDataFlag,
utils.MinerRecommitIntervalFlag,
utils.MinerNewPayloadTimeout,
utils.CeloFeeCurrencyDefault,
utils.CeloFeeCurrencyLimits,
utils.NATFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV4Flag,
Expand Down
46 changes: 46 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,17 @@ var (
Value: ethconfig.Defaults.Miner.NewPayloadTimeout,
Category: flags.MinerCategory,
}
CeloFeeCurrencyDefault = &cli.Float64Flag{
Name: "celo.feecurrency.default",
Usage: "Default fraction of block gas limit available for TXs paid with a whitelisted alternative currency",
Value: ethconfig.Defaults.Miner.FeeCurrencyDefault,
Category: flags.MinerCategory,
}
CeloFeeCurrencyLimits = &cli.StringFlag{
Name: "celo.feecurrency.limits",
Usage: "Comma separated currency address-to-block percentage mappings (<address>=<fraction>)",
Category: flags.MinerCategory,
}

// Account settings
UnlockedAccountFlag = &cli.StringFlag{
Expand Down Expand Up @@ -1642,6 +1653,39 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
}
}

func setCeloMiner(ctx *cli.Context, cfg *miner.Config, networkId uint64) {
cfg.FeeCurrencyDefault = ctx.Float64(CeloFeeCurrencyDefault.Name)

defaultLimits, ok := miner.DefaultFeeCurrencyLimits[networkId]
if !ok {
defaultLimits = make(map[common.Address]float64)
}

cfg.FeeCurrencyLimits = defaultLimits

if ctx.IsSet(CeloFeeCurrencyLimits.Name) {
feeCurrencyLimits := ctx.String(CeloFeeCurrencyLimits.Name)

for _, entry := range strings.Split(feeCurrencyLimits, ",") {
parts := strings.Split(entry, "=")
if len(parts) != 2 {
Fatalf("Invalid fee currency limits entry: %s", entry)
}
var address common.Address
if err := address.UnmarshalText([]byte(parts[0])); err != nil {
Fatalf("Invalid fee currency address hash %s: %v", parts[0], err)
}

fraction, err := strconv.ParseFloat(parts[1], 64)
if err != nil {
Fatalf("Invalid block limit fraction %s: %v", parts[1], err)
}

cfg.FeeCurrencyLimits[address] = fraction
}
}
}

func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) {
requiredBlocks := ctx.String(EthRequiredBlocksFlag.Name)
if requiredBlocks == "" {
Expand Down Expand Up @@ -1991,6 +2035,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if err := kzg4844.UseCKZG(ctx.String(CryptoKZGFlag.Name) == "ckzg"); err != nil {
Fatalf("Failed to set KZG library implementation to %s: %v", ctx.String(CryptoKZGFlag.Name), err)
}

setCeloMiner(ctx, &cfg.Miner, cfg.NetworkId)
}

// SetDNSDiscoveryDefaults configures DNS discovery with the given URL if
Expand Down
10 changes: 10 additions & 0 deletions common/celo_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ var (

type ExchangeRates = map[Address]*big.Rat

func CurrencyWhitelist(exchangeRates ExchangeRates) []Address {
addrs := make([]Address, len(exchangeRates))
i := 0
for k := range exchangeRates {
addrs[i] = k
i++
}
return addrs
}

func IsCurrencyWhitelisted(exchangeRates ExchangeRates, feeCurrency *Address) bool {
if feeCurrency == nil {
return true
Expand Down
71 changes: 71 additions & 0 deletions core/celo_multi_gaspool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package core

import (
"github.com/ethereum/go-ethereum/common"
)

type FeeCurrency = common.Address

// MultiGasPool tracks the amount of gas available during execution
// of the transactions in a block per fee currency. The zero value is a pool
// with zero gas available.
type MultiGasPool struct {
pools map[FeeCurrency]*GasPool
defaultPool *GasPool
}

type FeeCurrencyLimitMapping = map[FeeCurrency]float64

// NewMultiGasPool creates a multi-fee currency gas pool and a default fallback
// pool for CELO
func NewMultiGasPool(
blockGasLimit uint64,
whitelist []FeeCurrency,
defaultLimit float64,
limitsMapping FeeCurrencyLimitMapping,
) *MultiGasPool {
pools := make(map[FeeCurrency]*GasPool, len(whitelist))

for i := range whitelist {
currency := whitelist[i]
fraction, ok := limitsMapping[currency]
if !ok {
fraction = defaultLimit
}

pools[currency] = new(GasPool).AddGas(
uint64(float64(blockGasLimit) * fraction),
)
}

// A special case for CELO which doesn't have a limit
celoPool := new(GasPool).AddGas(blockGasLimit)

return &MultiGasPool{
pools: pools,
defaultPool: celoPool,
}
}

// PoolFor returns a configured pool for the given fee currency or the default
// one otherwise
func (mgp MultiGasPool) PoolFor(feeCurrency *FeeCurrency) *GasPool {
if feeCurrency == nil || mgp.pools[*feeCurrency] == nil {
return mgp.defaultPool
}

return mgp.pools[*feeCurrency]
}

func (mgp MultiGasPool) Copy() *MultiGasPool {
pools := make(map[FeeCurrency]*GasPool, len(mgp.pools))
for fc, gp := range mgp.pools {
gpCpy := *gp
pools[fc] = &gpCpy
}
gpCpy := *mgp.defaultPool
return &MultiGasPool{
pools: pools,
defaultPool: &gpCpy,
}
}
138 changes: 138 additions & 0 deletions core/celo_multi_gaspool_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package core

import (
"testing"

"github.com/ethereum/go-ethereum/common"
)

func TestMultiCurrencyGasPool(t *testing.T) {
blockGasLimit := uint64(1_000)
subGasAmount := 100

cUSDToken := common.HexToAddress("0x765DE816845861e75A25fCA122bb6898B8B1282a")
cEURToken := common.HexToAddress("0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73")

testCases := []struct {
name string
feeCurrency *FeeCurrency
whitelist []FeeCurrency
defaultLimit float64
limits FeeCurrencyLimitMapping
defaultPoolExpected bool
expectedValue uint64
}{
{
name: "Empty whitelist, empty mapping, CELO uses default pool",
feeCurrency: nil,
whitelist: []FeeCurrency{},
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{},
defaultPoolExpected: true,
expectedValue: 900, // blockGasLimit - subGasAmount
},
{
name: "Non-empty whitelist, non-empty mapping, CELO uses default pool",
feeCurrency: nil,
whitelist: []FeeCurrency{
cUSDToken,
},
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{
cUSDToken: 0.5,
},
defaultPoolExpected: true,
expectedValue: 900, // blockGasLimit - subGasAmount
},
{
name: "Empty whitelist, empty mapping, non-whitelisted currency fallbacks to the default pool",
feeCurrency: &cUSDToken,
whitelist: []FeeCurrency{},
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{},
defaultPoolExpected: true,
expectedValue: 900, // blockGasLimit - subGasAmount
},
{
name: "Non-empty whitelist, non-empty mapping, non-whitelisted currency uses default pool",
feeCurrency: &cEURToken,
whitelist: []FeeCurrency{
cUSDToken,
},
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{
cUSDToken: 0.5,
},
defaultPoolExpected: true,
expectedValue: 900, // blockGasLimit - subGasAmount
},
{
name: "Non-empty whitelist, empty mapping, whitelisted currency uses default limit",
feeCurrency: &cUSDToken,
whitelist: []FeeCurrency{
cUSDToken,
},
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{},
defaultPoolExpected: false,
expectedValue: 800, // blockGasLimit * defaultLimit - subGasAmount
},
{
name: "Non-empty whitelist, non-empty mapping, configured whitelisted currency uses configured limits",
feeCurrency: &cUSDToken,
whitelist: []FeeCurrency{
cUSDToken,
},
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{
cUSDToken: 0.5,
},
defaultPoolExpected: false,
expectedValue: 400, // blockGasLimit * 0.5 - subGasAmount
},
{
name: "Non-empty whitelist, non-empty mapping, unconfigured whitelisted currency uses default limit",
feeCurrency: &cEURToken,
whitelist: []FeeCurrency{
cUSDToken,
cEURToken,
},
defaultLimit: 0.9,
limits: map[FeeCurrency]float64{
cUSDToken: 0.5,
},
defaultPoolExpected: false,
expectedValue: 800, // blockGasLimit * 0.5 - subGasAmount
},
}

for _, c := range testCases {
t.Run(c.name, func(t *testing.T) {
mgp := NewMultiGasPool(
blockGasLimit,
c.whitelist,
c.defaultLimit,
c.limits,
)

pool := mgp.PoolFor(c.feeCurrency)
pool.SubGas(uint64(subGasAmount))

if c.defaultPoolExpected {
result := mgp.PoolFor(nil).Gas()
if result != c.expectedValue {
t.Error("Default pool expected", c.expectedValue, "got", result)
}
} else {
result := mgp.PoolFor(c.feeCurrency).Gas()

if result != c.expectedValue {
t.Error(
"Expected pool", c.feeCurrency, "value", c.expectedValue,
"got", result,
)
}
}
})
}
}
17 changes: 9 additions & 8 deletions core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,14 +573,15 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address]
lazies := make([]*txpool.LazyTransaction, len(txs))
for i := 0; i < len(txs); i++ {
lazies[i] = &txpool.LazyTransaction{
Pool: pool,
Hash: txs[i].Hash(),
Tx: txs[i],
Time: txs[i].Time(),
GasFeeCap: uint256.MustFromBig(txs[i].GasFeeCap()),
GasTipCap: uint256.MustFromBig(txs[i].GasTipCap()),
Gas: txs[i].Gas(),
BlobGas: txs[i].BlobGas(),
Pool: pool,
Hash: txs[i].Hash(),
Tx: txs[i],
Time: txs[i].Time(),
GasFeeCap: uint256.MustFromBig(txs[i].GasFeeCap()),
GasTipCap: uint256.MustFromBig(txs[i].GasTipCap()),
Gas: txs[i].Gas(),
BlobGas: txs[i].BlobGas(),
FeeCurrency: txs[i].FeeCurrency(),
}
}
pending[addr] = lazies
Expand Down
3 changes: 3 additions & 0 deletions core/txpool/subpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ type LazyTransaction struct {

Gas uint64 // Amount of gas required by the transaction
BlobGas uint64 // Amount of blob gas required by the transaction

// Celo
FeeCurrency *common.Address
}

// Resolve retrieves the full transaction belonging to a lazy handle if it is still
Expand Down
15 changes: 8 additions & 7 deletions eth/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,14 @@ func (p *testTxPool) Pending(filter txpool.PendingFilter) map[common.Address][]*
for addr, batch := range batches {
for _, tx := range batch {
pending[addr] = append(pending[addr], &txpool.LazyTransaction{
Hash: tx.Hash(),
Tx: tx,
Time: tx.Time(),
GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
GasTipCap: uint256.MustFromBig(tx.GasTipCap()),
Gas: tx.Gas(),
BlobGas: tx.BlobGas(),
Hash: tx.Hash(),
Tx: tx,
Time: tx.Time(),
GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
GasTipCap: uint256.MustFromBig(tx.GasTipCap()),
Gas: tx.Gas(),
BlobGas: tx.BlobGas(),
FeeCurrency: tx.FeeCurrency(),
})
}
}
Expand Down
29 changes: 29 additions & 0 deletions miner/celo_defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package miner

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
)

// cStables addresses on mainnet
var (
cUSD_TOKEN = common.HexToAddress("0x765DE816845861e75A25fCA122bb6898B8B1282a")
cEUR_TOKEN = common.HexToAddress("0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73")
cREAL_TOKEN = common.HexToAddress("0xe8537a3d056DA446677B9E9d6c5dB704EaAb4787")
USDC_TOKEN = common.HexToAddress("0xcebA9300f2b948710d2653dD7B07f33A8B32118C")
USDT_TOKEN = common.HexToAddress("0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e")
)

// default limits default fraction
const DefaultFeeCurrencyLimit = 0.5

// default limits configuration
var DefaultFeeCurrencyLimits = map[uint64]map[common.Address]float64{
params.CeloMainnetChainID: {
cUSD_TOKEN: 0.9,
USDT_TOKEN: 0.9,
USDC_TOKEN: 0.9,
cEUR_TOKEN: 0.5,
cREAL_TOKEN: 0.5,
},
}
Loading

0 comments on commit 7bac150

Please sign in to comment.