diff --git a/core/genesis.go b/core/genesis.go index ac332bb052..9c0815d4fe 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -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 diff --git a/core/genesis_extra_test.go b/core/genesis_extra_test.go new file mode 100644 index 0000000000..9f69c52a6f --- /dev/null +++ b/core/genesis_extra_test.go @@ -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( + ¶ms.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) +} diff --git a/params/extras/config.go b/params/extras/config.go index 4a3d267e95..f27847e402 100644 --- a/params/extras/config.go +++ b/params/extras/config.go @@ -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 } @@ -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) diff --git a/params/extras/config_test.go b/params/extras/config_test.go new file mode 100644 index 0000000000..f7665c1d3c --- /dev/null +++ b/params/extras/config_test.go @@ -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()) + } + }) + } +} diff --git a/params/extras/precompile_config_test.go b/params/extras/precompile_config_test.go index 53dba314d9..ef84db1b5a 100644 --- a/params/extras/precompile_config_test.go +++ b/params/extras/precompile_config_test.go @@ -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), diff --git a/params/extras/precompile_upgrade_test.go b/params/extras/precompile_upgrade_test.go index 859fd964f7..58f17991bf 100644 --- a/params/extras/precompile_upgrade_test.go +++ b/params/extras/precompile_upgrade_test.go @@ -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), }