Skip to content
7 changes: 7 additions & 0 deletions core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ func SetupGenesisBlock(
customrawdb.WriteChainConfig(db, stored, newcfg)
return newcfg, stored, nil
}

// Notes on the following line:
// - this is needed in coreth to handle the case where existing nodes do not
// have the Berlin or London forks initialized by block number on disk.
// See https://github.com/ava-labs/coreth/pull/667/files
// - this is not needed in subnet-evm but it does not impact it either
params.SetEthUpgrades(storedcfg, params.GetExtra(storedcfg).NetworkUpgrades)
// Check config compatibility and write the config. Compatibility errors
// are returned to the caller unless we're already at block zero.
// we use last accepted block for cfg compatibility check. Note this allows
Expand Down
77 changes: 77 additions & 0 deletions core/genesis_extra_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// (c) 2025 Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package core

import (
"math/big"
"testing"
"time"

"github.com/ava-labs/libevm/common"
"github.com/ava-labs/libevm/core/rawdb"
"github.com/ava-labs/libevm/core/types"
"github.com/ava-labs/libevm/triedb"
"github.com/ava-labs/subnet-evm/commontype"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/params/extras"
"github.com/ava-labs/subnet-evm/utils"
"github.com/stretchr/testify/require"
)

func TestGenesisEthUpgrades(t *testing.T) {
db := rawdb.NewMemoryDatabase()
preEthUpgrades := params.WithExtra(
&params.ChainConfig{
ChainID: big.NewInt(43114), // Specifically refers to mainnet for this UT
HomesteadBlock: big.NewInt(0),
// For this test to be a proper regression test, DAOForkBlock and
// DAOForkSupport should be set to match the values in
// [params.SetEthUpgrades]. Otherwise, in case of a regression, the test
// would pass as there would be a mismatch at genesis, which is
// incorrectly considered a success.
DAOForkBlock: big.NewInt(0),
DAOForkSupport: true,
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
},
&extras.ChainConfig{
FeeConfig: commontype.FeeConfig{
MinBaseFee: big.NewInt(1),
},
NetworkUpgrades: extras.NetworkUpgrades{
SubnetEVMTimestamp: utils.NewUint64(0),
},
},
)
tdb := triedb.NewDatabase(db, triedb.HashDefaults)
config := *preEthUpgrades
// Set this up once, just to get the genesis hash
_, genHash, err := SetupGenesisBlock(db, tdb, &Genesis{Config: &config}, common.Hash{}, false)
require.NoError(t, err)
// Write the configuration back to the db as it would be in prior versions
rawdb.WriteChainConfig(db, genHash, preEthUpgrades)
// Make some other block
block := types.NewBlock(
&types.Header{
Number: big.NewInt(1640340), // Berlin activation on mainnet
Difficulty: big.NewInt(1),
ParentHash: genHash,
Time: uint64(time.Now().Unix()),
},
nil, nil, nil, nil,
)
rawdb.WriteBlock(db, block)
// We should still be able to re-initialize
config = *preEthUpgrades
avalancheUpgrades := extras.NetworkUpgrades{}
params.SetEthUpgrades(&config, avalancheUpgrades) // New versions will set additional fields eg, LondonBlock
_, _, err = SetupGenesisBlock(db, tdb, &Genesis{Config: &config}, block.Hash(), false)
require.NoError(t, err)
}
13 changes: 13 additions & 0 deletions params/extras/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,15 @@ func (c *ChainConfig) Description() string {
}
banner += fmt.Sprintf("Upgrade Config: %s", string(upgradeConfigBytes))
banner += "\n"

feeBytes, err := json.Marshal(c.FeeConfig)
if err != nil {
feeBytes = []byte("cannot marshal FeeConfig")
}
banner += fmt.Sprintf("Fee Config: %s\n", string(feeBytes))

banner += fmt.Sprintf("Allow Fee Recipients: %v\n", c.AllowFeeRecipients)

return banner
}

Expand Down Expand Up @@ -318,6 +327,10 @@ func checkForks(forks []fork, blockFork bool) error {

// Verify verifies chain config.
func (c *ChainConfig) Verify() error {
if err := c.FeeConfig.Verify(); err != nil {
return fmt.Errorf("invalid fee config: %w", err)
}

// Verify the precompile upgrades are internally consistent given the existing chainConfig.
if err := c.verifyPrecompileUpgrades(); err != nil {
return fmt.Errorf("invalid precompile upgrades: %w", err)
Expand Down
185 changes: 185 additions & 0 deletions params/extras/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// (c) 2025 Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package extras

import (
"math/big"
"testing"
"time"

"github.com/ava-labs/avalanchego/snow"
"github.com/ava-labs/avalanchego/upgrade"
"github.com/ava-labs/libevm/common"
"github.com/ava-labs/subnet-evm/commontype"
"github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func pointer[T any](v T) *T { return &v }

func TestChainConfigDescription(t *testing.T) {
t.Parallel()

tests := map[string]struct {
config *ChainConfig
wantRegex string
}{
"nil": {},
"empty": {
config: &ChainConfig{},
wantRegex: `Avalanche Upgrades \(timestamp based\)\:
- SubnetEVM Timestamp: ( )+@nil( )+\(https:\/\/github\.com\/ava-labs\/avalanchego\/releases\/tag\/v1\.10\.0\)
( - .+Timestamp: .+\n)+
Upgrade Config: {}
Fee Config: {}
Allow Fee Recipients: false
$`,
},
"set": {
config: &ChainConfig{
NetworkUpgrades: NetworkUpgrades{
SubnetEVMTimestamp: pointer(uint64(1)),
DurangoTimestamp: pointer(uint64(2)),
EtnaTimestamp: pointer(uint64(3)),
FortunaTimestamp: pointer(uint64(4)),
},
FeeConfig: commontype.FeeConfig{
GasLimit: big.NewInt(5),
TargetBlockRate: 6,
MinBaseFee: big.NewInt(7),
TargetGas: big.NewInt(8),
BaseFeeChangeDenominator: big.NewInt(9),
MinBlockGasCost: big.NewInt(10),
MaxBlockGasCost: big.NewInt(11),
BlockGasCostStep: big.NewInt(12),
},
AllowFeeRecipients: true,
UpgradeConfig: UpgradeConfig{
NetworkUpgradeOverrides: &NetworkUpgrades{
SubnetEVMTimestamp: pointer(uint64(13)),
},
StateUpgrades: []StateUpgrade{
{
BlockTimestamp: pointer(uint64(14)),
StateUpgradeAccounts: map[common.Address]StateUpgradeAccount{
common.Address{15}: {
Code: []byte{16},
},
},
},
},
},
},
wantRegex: `Avalanche Upgrades \(timestamp based\)\:
- SubnetEVM Timestamp: ( )+@1( )+\(https:\/\/github\.com\/ava-labs\/avalanchego\/releases\/tag\/v1\.10\.0\)
( - .+Timestamp: .+\n)+
Upgrade Config: {"networkUpgradeOverrides":{"subnetEVMTimestamp":13},"stateUpgrades":\[{"blockTimestamp":14,"accounts":{"0x0f00000000000000000000000000000000000000":{"code":"0x10"}}}\]}
Fee Config: {"gasLimit":5,"targetBlockRate":6,"minBaseFee":7,"targetGas":8,"baseFeeChangeDenominator":9,"minBlockGasCost":10,"maxBlockGasCost":11,"blockGasCostStep":12}
Allow Fee Recipients: true
$`,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
got := test.config.Description()
assert.Regexp(t, test.wantRegex, got, "config description mismatch")
})
}
}

func TestChainConfigVerify(t *testing.T) {
t.Parallel()

validFeeConfig := commontype.FeeConfig{
GasLimit: big.NewInt(1),
TargetBlockRate: 1,
MinBaseFee: big.NewInt(1),
TargetGas: big.NewInt(1),
BaseFeeChangeDenominator: big.NewInt(1),
MinBlockGasCost: big.NewInt(1),
MaxBlockGasCost: big.NewInt(1),
BlockGasCostStep: big.NewInt(1),
}

tests := map[string]struct {
config ChainConfig
errRegex string
}{
"invalid_feeconfig": {
config: ChainConfig{
FeeConfig: commontype.FeeConfig{
GasLimit: nil,
},
},
errRegex: "^invalid fee config: ",
},
"invalid_precompile_upgrades": {
// Also see precompile_config_test.go TestVerifyWithChainConfig* tests
config: ChainConfig{
FeeConfig: validFeeConfig,
UpgradeConfig: UpgradeConfig{
PrecompileUpgrades: []PrecompileUpgrade{
// same precompile cannot be configured twice for the same timestamp
{Config: txallowlist.NewDisableConfig(pointer(uint64(1)))},
{Config: txallowlist.NewDisableConfig(pointer(uint64(1)))},
},
},
},
errRegex: "^invalid precompile upgrades: ",
},
"invalid_state_upgrades": {
config: ChainConfig{
FeeConfig: validFeeConfig,
UpgradeConfig: UpgradeConfig{
StateUpgrades: []StateUpgrade{
{BlockTimestamp: nil},
},
},
},
errRegex: "^invalid state upgrades: ",
},
"invalid_network_upgrades": {
config: ChainConfig{
FeeConfig: validFeeConfig,
NetworkUpgrades: NetworkUpgrades{
SubnetEVMTimestamp: nil,
},
AvalancheContext: AvalancheContext{SnowCtx: &snow.Context{}},
},
errRegex: "^invalid network upgrades: ",
},
"valid": {
config: ChainConfig{
FeeConfig: validFeeConfig,
NetworkUpgrades: NetworkUpgrades{
SubnetEVMTimestamp: pointer(uint64(1)),
DurangoTimestamp: pointer(uint64(2)),
EtnaTimestamp: pointer(uint64(3)),
FortunaTimestamp: pointer(uint64(4)),
},
AvalancheContext: AvalancheContext{SnowCtx: &snow.Context{
NetworkUpgrades: upgrade.Config{
DurangoTime: time.Unix(2, 0),
EtnaTime: time.Unix(3, 0),
FortunaTime: time.Unix(4, 0),
},
}},
},
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
err := test.config.Verify()
if test.errRegex == "" {
assert.NoError(t, err)
} else {
require.Error(t, err)
assert.Regexp(t, test.errRegex, err.Error())
}
})
}
}
4 changes: 3 additions & 1 deletion params/extras/precompile_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,9 @@ func TestVerifyPrecompiles(t *testing.T) {

func TestVerifyRequiresSortedTimestamps(t *testing.T) {
admins := []common.Address{{1}}
config := &ChainConfig{}
config := &ChainConfig{
FeeConfig: DefaultFeeConfig,
}
config.PrecompileUpgrades = []PrecompileUpgrade{
{
Config: txallowlist.NewConfig(utils.NewUint64(2), admins, nil, nil),
Expand Down
4 changes: 3 additions & 1 deletion params/extras/precompile_upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import (

func TestVerifyUpgradeConfig(t *testing.T) {
admins := []common.Address{{1}}
chainConfig := &ChainConfig{}
chainConfig := &ChainConfig{
FeeConfig: DefaultFeeConfig,
}
chainConfig.GenesisPrecompiles = Precompiles{
txallowlist.ConfigKey: txallowlist.NewConfig(utils.NewUint64(1), admins, nil, nil),
}
Expand Down