From 7bac1501d71750477c28555ce8494215c08b65b6 Mon Sep 17 00:00:00 2001
From: Maximilian Langenfeld <15726643+ezdac@users.noreply.github.com>
Date: Tue, 4 Jun 2024 10:27:27 +0200
Subject: [PATCH] Port the block limit per fee currency (#119)
* 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
(https://github.com/celo-org/celo-blockchain/commit/dc45bdc00).
* Copy MultiGasPool in miner environment
* Add USDT and USDC to block-limit per currency defaults
* Initialize MultiGasPool with currency whitelist
---
cmd/geth/main.go | 2 +
cmd/utils/flags.go | 46 +++++++++
common/celo_types.go | 10 ++
core/celo_multi_gaspool.go | 71 ++++++++++++++
core/celo_multi_gaspool_test.go | 138 +++++++++++++++++++++++++++
core/txpool/legacypool/legacypool.go | 17 ++--
core/txpool/subpool.go | 3 +
eth/handler_test.go | 15 +--
miner/celo_defaults.go | 29 ++++++
miner/miner.go | 6 ++
miner/worker.go | 66 +++++++++++--
params/config.go | 1 +
12 files changed, 383 insertions(+), 21 deletions(-)
create mode 100644 core/celo_multi_gaspool.go
create mode 100644 core/celo_multi_gaspool_test.go
create mode 100644 miner/celo_defaults.go
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 0418dd685a..e91ca41a21 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -130,6 +130,8 @@ var (
utils.MinerExtraDataFlag,
utils.MinerRecommitIntervalFlag,
utils.MinerNewPayloadTimeout,
+ utils.CeloFeeCurrencyDefault,
+ utils.CeloFeeCurrencyLimits,
utils.NATFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV4Flag,
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 0c5e4414eb..960f598bf7 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -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 (
=)",
+ Category: flags.MinerCategory,
+ }
// Account settings
UnlockedAccountFlag = &cli.StringFlag{
@@ -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 == "" {
@@ -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
diff --git a/common/celo_types.go b/common/celo_types.go
index 44ac2ef072..3ae8a2935a 100644
--- a/common/celo_types.go
+++ b/common/celo_types.go
@@ -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
diff --git a/core/celo_multi_gaspool.go b/core/celo_multi_gaspool.go
new file mode 100644
index 0000000000..6cec29a1c9
--- /dev/null
+++ b/core/celo_multi_gaspool.go
@@ -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,
+ }
+}
diff --git a/core/celo_multi_gaspool_test.go b/core/celo_multi_gaspool_test.go
new file mode 100644
index 0000000000..ec79703329
--- /dev/null
+++ b/core/celo_multi_gaspool_test.go
@@ -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,
+ )
+ }
+ }
+ })
+ }
+}
diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go
index 0e40c0c05d..a2717d8cfa 100644
--- a/core/txpool/legacypool/legacypool.go
+++ b/core/txpool/legacypool/legacypool.go
@@ -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
diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go
index 9881ed1b8f..5e0c850fd0 100644
--- a/core/txpool/subpool.go
+++ b/core/txpool/subpool.go
@@ -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
diff --git a/eth/handler_test.go b/eth/handler_test.go
index 58353f6b64..8c1ba1ef5f 100644
--- a/eth/handler_test.go
+++ b/eth/handler_test.go
@@ -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(),
})
}
}
diff --git a/miner/celo_defaults.go b/miner/celo_defaults.go
new file mode 100644
index 0000000000..be843d1bc9
--- /dev/null
+++ b/miner/celo_defaults.go
@@ -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,
+ },
+}
diff --git a/miner/miner.go b/miner/miner.go
index 85b3d8d13d..2b9c1a416c 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -62,6 +62,10 @@ type Config struct {
RollupComputePendingBlock bool // Compute the pending block from tx-pool, instead of copying the latest-block
EffectiveGasCeil uint64 // if non-zero, a gas ceiling to apply independent of the header's gaslimit value
+
+ // Celo:
+ FeeCurrencyDefault float64 // Default fraction of block gas limit
+ FeeCurrencyLimits map[common.Address]float64 // Fee currency-to-limit fraction mapping
}
// DefaultConfig contains default settings for miner.
@@ -75,6 +79,8 @@ var DefaultConfig = Config{
// run 3 rounds.
Recommit: 2 * time.Second,
NewPayloadTimeout: 2 * time.Second,
+
+ FeeCurrencyDefault: DefaultFeeCurrencyLimit,
}
// Miner creates blocks and searches for proof-of-work values.
diff --git a/miner/worker.go b/miner/worker.go
index a69e564410..7a21bef643 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -87,11 +87,13 @@ var (
// environment is the worker's current environment and holds all
// information of the sealing block generation.
type environment struct {
- signer types.Signer
- state *state.StateDB // apply state changes here
- tcount int // tx count in cycle
- gasPool *core.GasPool // available gas used to pack transactions
- coinbase common.Address
+ signer types.Signer
+ state *state.StateDB // apply state changes here
+ tcount int // tx count in cycle
+ gasPool *core.GasPool // available gas used to pack transactions
+ multiGasPool *core.MultiGasPool // available per-fee-currency gas used to pack transactions
+ feeCurrencyWhitelist []common.Address
+ coinbase common.Address
header *types.Header
txs []*types.Transaction
@@ -114,6 +116,9 @@ func (env *environment) copy() *environment {
gasPool := *env.gasPool
cpy.gasPool = &gasPool
}
+ if env.multiGasPool != nil {
+ cpy.multiGasPool = env.multiGasPool.Copy()
+ }
cpy.txs = make([]*types.Transaction, len(env.txs))
copy(cpy.txs, env.txs)
@@ -851,6 +856,14 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac
if env.gasPool == nil {
env.gasPool = new(core.GasPool).AddGas(gasLimit)
}
+ if env.multiGasPool == nil {
+ env.multiGasPool = core.NewMultiGasPool(
+ env.header.GasLimit,
+ env.feeCurrencyWhitelist,
+ w.config.FeeCurrencyDefault,
+ w.config.FeeCurrencyLimits,
+ )
+ }
var coalescedLogs []*types.Log
for {
@@ -906,6 +919,15 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac
txs.Pop()
continue
}
+ if left := env.multiGasPool.PoolFor(ltx.FeeCurrency).Gas(); left < ltx.Gas {
+ log.Trace(
+ "Not enough specific fee-currency gas left for transaction",
+ "currency", ltx.FeeCurrency, "hash", ltx.Hash,
+ "left", left, "needed", ltx.Gas,
+ )
+ txs.Pop()
+ continue
+ }
// Transaction seems to fit, pull it up from the pool
tx := ltx.Resolve()
if tx == nil {
@@ -927,7 +949,9 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac
// Start executing the transaction
env.state.SetTxContext(tx.Hash(), env.tcount)
+ availableGas := env.gasPool.Gas()
logs, err := w.commitTransaction(env, tx)
+ gasUsed := availableGas - env.gasPool.Gas()
switch {
case errors.Is(err, core.ErrNonceTooLow):
// New head notification data race between the transaction pool and miner, shift
@@ -935,6 +959,23 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac
txs.Shift()
case errors.Is(err, nil):
+ err := env.multiGasPool.PoolFor(tx.FeeCurrency()).SubGas(gasUsed)
+ if err != nil {
+ // Should never happen as we check it above
+ log.Warn(
+ "Unexpectedly reached limit for fee currency, but tx will not be skipped",
+ "hash", tx.Hash(), "gas", env.multiGasPool.PoolFor(tx.FeeCurrency()).Gas(),
+ "tx gas used", gasUsed,
+ )
+ // If we reach this codepath, we want to still include the transaction,
+ // since the "global" gasPool in the commitTransaction accepted it and we
+ // would have to roll the transaction back now, introducing unnecessary
+ // complexity.
+ // Since we shouldn't reach this point anyways and the
+ // block gas limit per fee currency is enforced voluntarily and not
+ // included in the consensus this is fine.
+ }
+
// Everything ok, collect the logs and shift in the next transaction from the same account
coalescedLogs = append(coalescedLogs, logs...)
env.tcount++
@@ -1095,8 +1136,9 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
log.Error("Failed to create sealing context", "err", err)
return nil, err
}
+ context := core.NewEVMBlockContext(header, w.chain, nil, w.chainConfig, env.state)
+ env.feeCurrencyWhitelist = common.CurrencyWhitelist(context.ExchangeRates)
if header.ParentBeaconRoot != nil {
- context := core.NewEVMBlockContext(header, w.chain, nil, w.chainConfig, env.state)
vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, w.chainConfig, vm.Config{})
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state)
}
@@ -1175,6 +1217,14 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult {
}
work.gasPool = new(core.GasPool).AddGas(gasLimit)
}
+ if work.multiGasPool == nil {
+ work.multiGasPool = core.NewMultiGasPool(
+ work.header.GasLimit,
+ work.feeCurrencyWhitelist,
+ w.config.FeeCurrencyDefault,
+ w.config.FeeCurrencyLimits,
+ )
+ }
misc.EnsureCreate2Deployer(w.chainConfig, work.header.Time, work.state)
@@ -1185,6 +1235,10 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult {
if err != nil {
return &newPayloadResult{err: fmt.Errorf("failed to force-include tx: %s type: %d sender: %s nonce: %d, err: %w", tx.Hash(), tx.Type(), from, tx.Nonce(), err)}
}
+ // the non-fee currency pool in the multipool is not used, but for consistency
+ // subtract the gas. Don't check the error either, this has been checked already
+ // with the work.gasPool.
+ work.multiGasPool.PoolFor(nil).SubGas(tx.Gas())
work.tcount++
}
diff --git a/params/config.go b/params/config.go
index f067b37a23..823ace9fb2 100644
--- a/params/config.go
+++ b/params/config.go
@@ -35,6 +35,7 @@ var (
const (
OPMainnetChainID = 10
OPGoerliChainID = 420
+ CeloMainnetChainID = 42220
BaseMainnetChainID = 8453
BaseGoerliChainID = 84531
baseSepoliaChainID = 84532