From e03286abd210dd7d9112fe22e9716f589d62ff25 Mon Sep 17 00:00:00 2001 From: Yoshihito Aso Date: Wed, 8 Jul 2026 10:36:47 +0900 Subject: [PATCH 1/3] Add EIP-7997 factory --- consensus/misc/eip7997.go | 39 ++++++++ core/chain_makers.go | 3 + core/eip7997_test.go | 201 ++++++++++++++++++++++++++++++++++++++ core/state_processor.go | 14 +++ params/config.go | 26 +++-- params/protocol_params.go | 10 +- 6 files changed, 285 insertions(+), 8 deletions(-) create mode 100644 consensus/misc/eip7997.go create mode 100644 core/eip7997_test.go diff --git a/consensus/misc/eip7997.go b/consensus/misc/eip7997.go new file mode 100644 index 000000000..eee8190c7 --- /dev/null +++ b/consensus/misc/eip7997.go @@ -0,0 +1,39 @@ +// Copyright 2026 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package misc + +import ( + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +// ApplyEIP7997 inserts the deterministic deployment factory into the state as an +// irregular state transition, as specified by EIP-7997. +func ApplyEIP7997(statedb *state.StateDB) { + wantHash := crypto.Keccak256Hash(params.DeterministicFactoryCode) + if statedb.GetCodeHash(params.DeterministicFactoryAddress) == wantHash { + return + } + if !statedb.Exist(params.DeterministicFactoryAddress) { + statedb.CreateAccount(params.DeterministicFactoryAddress) + } + statedb.SetCode(params.DeterministicFactoryAddress, params.DeterministicFactoryCode) + if statedb.GetNonce(params.DeterministicFactoryAddress) == 0 { + statedb.SetNonce(params.DeterministicFactoryAddress, 1) + } +} diff --git a/core/chain_makers.go b/core/chain_makers.go index e02373e8b..cc2177ccd 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -224,6 +224,9 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 { misc.ApplyDAOHardFork(statedb) } + if config.IsAmsterdam(b.header.Number) && !config.IsAmsterdam(parent.Number()) { + misc.ApplyEIP7997(statedb) + } // Execute any user modifications to the block if gen != nil { gen(i, b) diff --git a/core/eip7997_test.go b/core/eip7997_test.go new file mode 100644 index 000000000..56f35191e --- /dev/null +++ b/core/eip7997_test.go @@ -0,0 +1,201 @@ +// Copyright 2026 BOOSTRY Co., Ltd. +// This file is part of ibet-Core. +// +// ibet-Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ibet-Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with ibet-Core. If not, see . + +package core + +import ( + "bytes" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +func newEIP7997State(t *testing.T) (*state.StateDB, state.Database) { + t.Helper() + + db := state.NewDatabase(rawdb.NewMemoryDatabase()) + statedb, err := state.New(common.Hash{}, db, nil) + if err != nil { + t.Fatal(err) + } + return statedb, db +} + +func stateAtRoot(t *testing.T, db state.Database, root common.Hash) *state.StateDB { + t.Helper() + + statedb, err := state.New(root, db, nil) + if err != nil { + t.Fatal(err) + } + return statedb +} + +func amsterdamTestConfig(block uint64) *params.ChainConfig { + config := *params.TestChainConfig + config.AmsterdamBlock = new(big.Int).SetUint64(block) + return &config +} + +// Verify that ApplyEIP7997 installs the factory code and nonce=1 into an empty public state. +func TestApplyEIP7997(t *testing.T) { + statedb, _ := newEIP7997State(t) + + // Apply the irregular state transition to an address that does not exist yet. + misc.ApplyEIP7997(statedb) + + if got := statedb.GetCode(params.DeterministicFactoryAddress); !bytes.Equal(got, params.DeterministicFactoryCode) { + t.Fatalf("factory code mismatch:\n got %x\nwant %x", got, params.DeterministicFactoryCode) + } + if got := statedb.GetNonce(params.DeterministicFactoryAddress); got != 1 { + t.Fatalf("factory nonce = %d, want 1", got) + } +} + +// Verify that ApplyEIP7997 preserves an existing non-zero nonce when canonical code is already installed. +func TestApplyEIP7997Existing(t *testing.T) { + statedb, _ := newEIP7997State(t) + + // Simulate a state where the factory has already been installed and used. + statedb.SetCode(params.DeterministicFactoryAddress, params.DeterministicFactoryCode) + statedb.SetNonce(params.DeterministicFactoryAddress, 5) + + misc.ApplyEIP7997(statedb) + + if got := statedb.GetNonce(params.DeterministicFactoryAddress); got != 5 { + t.Fatalf("existing factory nonce overwritten: got %d, want 5", got) + } +} + +// Verify that ApplyEIP7997 replaces wrong factory code with canonical code while preserving a non-zero nonce. +func TestApplyEIP7997WrongCode(t *testing.T) { + statedb, _ := newEIP7997State(t) + + // Simulate a pre-existing account at the factory address with unexpected code. + statedb.SetCode(params.DeterministicFactoryAddress, []byte{0x60, 0x00}) + statedb.SetNonce(params.DeterministicFactoryAddress, 7) + + misc.ApplyEIP7997(statedb) + + if got := statedb.GetCode(params.DeterministicFactoryAddress); !bytes.Equal(got, params.DeterministicFactoryCode) { + t.Fatalf("factory code not overwritten:\n got %x\nwant %x", got, params.DeterministicFactoryCode) + } + if got := statedb.GetNonce(params.DeterministicFactoryAddress); got != 7 { + t.Fatalf("factory nonce = %d, want 7", got) + } +} + +// Verify that the installed factory can deploy runtime code with CREATE2 from salt and initcode. +func TestEIP7997FactoryDeploys(t *testing.T) { + statedb, _ := newEIP7997State(t) + misc.ApplyEIP7997(statedb) + + var ( + caller = common.Address{0xca} + salt [32]byte + initcode = common.FromHex("60fe60005360016000f3") + ) + salt[31] = 0x42 + + // The factory calldata is 32 bytes of CREATE2 salt followed by the initcode. + // This initcode deploys a one-byte runtime code: 0xfe. + input := append(append([]byte{}, salt[:]...), initcode...) + blockContext := vm.BlockContext{ + CanTransfer: CanTransfer, + Transfer: Transfer, + BlockNumber: big.NewInt(1), + GasLimit: 10_000_000, + } + txContext := vm.TxContext{Origin: caller, GasPrice: new(big.Int)} + config := amsterdamTestConfig(0) + evm := vm.NewEVM(blockContext, txContext, statedb, statedb, config, vm.Config{}) + + // The test executes with Amsterdam rules, so the factory account must be in the + // access list before calling it directly through the EVM. + statedb.PrepareAccessList(caller, ¶ms.DeterministicFactoryAddress, vm.ActivePrecompiles(config.Rules(blockContext.BlockNumber)), nil) + + ret, _, err := evm.Call(vm.AccountRef(caller), params.DeterministicFactoryAddress, input, 10_000_000, new(big.Int)) + if err != nil { + t.Fatalf("factory call failed: %v", err) + } + + // The factory returns the deployed address, which should match the CREATE2 + // address derived from the factory address, salt, and initcode hash. + want := crypto.CreateAddress2(params.DeterministicFactoryAddress, salt, crypto.Keccak256(initcode)) + if len(ret) != 20 { + t.Fatalf("factory returned %d bytes, want 20", len(ret)) + } + if got := common.BytesToAddress(ret); got != want { + t.Fatalf("factory returned address %x, want %x", got, want) + } + if code := statedb.GetCode(want); !bytes.Equal(code, []byte{0xfe}) { + t.Fatalf("deployed runtime code = %x, want fe", code) + } +} + +// Verify that chain generation includes the factory in the state root only at the Amsterdam transition block. +func TestEIP7997AmsterdamTransition(t *testing.T) { + config := amsterdamTestConfig(1) + db := rawdb.NewMemoryDatabase() + genesis := (&Genesis{Config: config, Alloc: GenesisAlloc{}, Difficulty: big.NewInt(1)}).MustCommit(db) + + // The factory must not be present before the Amsterdam transition block. + statedb := stateAtRoot(t, state.NewDatabase(db), genesis.Root()) + if code := statedb.GetCode(params.DeterministicFactoryAddress); len(code) != 0 { + t.Fatalf("factory code present before amsterdam transition: %x", code) + } + + // Generate block 1, where Amsterdam activates, and check the generated state root. + blocks, _ := GenerateChain(config, genesis, ethash.NewFaker(), db, 1, nil) + statedb = stateAtRoot(t, state.NewDatabase(db), blocks[0].Root()) + if got := statedb.GetCode(params.DeterministicFactoryAddress); !bytes.Equal(got, params.DeterministicFactoryCode) { + t.Fatalf("factory code missing from generated amsterdam block: got %x", got) + } +} + +// Verify that block processing inserts the factory into public state at the Amsterdam transition block. +func TestEIP7997AmsterdamTransitionBlockProcessing(t *testing.T) { + config := amsterdamTestConfig(1) + db := rawdb.NewMemoryDatabase() + genesis := (&Genesis{Config: config, Alloc: GenesisAlloc{}, Difficulty: big.NewInt(1)}).MustCommit(db) + + // InsertChain reprocesses the generated block, exercising StateProcessor + // instead of only trusting the state root produced by GenerateChain. + blockchain, err := NewBlockChain(db, nil, config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) + if err != nil { + t.Fatal(err) + } + defer blockchain.Stop() + + blocks, _ := GenerateChain(config, genesis, ethash.NewFaker(), db, 1, nil) + if _, err := blockchain.InsertChain(blocks); err != nil { + t.Fatal(err) + } + + // After processing block 1, the canonical head state should contain the factory. + statedb := stateAtRoot(t, state.NewDatabase(db), blockchain.CurrentBlock().Root()) + if got := statedb.GetCode(params.DeterministicFactoryAddress); !bytes.Equal(got, params.DeterministicFactoryCode) { + t.Fatalf("factory code missing after block processing: got %x", got) + } +} diff --git a/core/state_processor.go b/core/state_processor.go index 11455f079..c809a9e73 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -80,6 +80,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, pri if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { misc.ApplyDAOHardFork(statedb) } + if isEIP7997Transition(p.config, p.bc, header) { + misc.ApplyEIP7997(statedb) + } blockContext := NewEVMBlockContext(header, p.bc, nil) // Iterate over and process the individual transactions for i, tx := range block.Transactions() { @@ -152,6 +155,17 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, pri return receipts, privateReceipts, allLogs, *usedGas, nil } +func isEIP7997Transition(config *params.ChainConfig, chain ChainContext, header *types.Header) bool { + if header.Number.Sign() == 0 || !config.IsAmsterdam(header.Number) { + return false + } + parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) + if parent == nil { + return false + } + return !config.IsAmsterdam(parent.Number) +} + // Quorum func HandlePrivateReceipt(receipt *types.Receipt, privateReceipt *types.Receipt, mpsReceipt *types.Receipt, tx *types.Transaction, privateStateDB *state.StateDB, privateStateRepo mps.PrivateStateRepository, bc *BlockChain) (*types.Receipt, []*types.Log) { var ( diff --git a/params/config.go b/params/config.go index b6352baca..b3ea6def6 100644 --- a/params/config.go +++ b/params/config.go @@ -248,21 +248,21 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, false, 32, 35, big.NewInt(0), big.NewInt(0), nil, nil, false, nil, nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, false, 32, 35, big.NewInt(0), big.NewInt(0), nil, nil, false, nil, nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil, nil, nil, nil, false, 32, 32, big.NewInt(0), big.NewInt(0), nil, nil, false, nil, nil} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil, nil, nil, nil, false, 32, 32, big.NewInt(0), big.NewInt(0), nil, nil, false, nil, nil} // Quorum chainID should 10 - TestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, false, 32, 32, big.NewInt(0), big.NewInt(0), nil, nil, false, nil, nil} + TestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, false, 32, 32, big.NewInt(0), big.NewInt(0), nil, nil, false, nil, nil} TestRules = TestChainConfig.Rules(new(big.Int)) - QuorumTestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, true, 64, 32, big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), false, nil, nil} - QuorumMPSTestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, true, 64, 32, big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), true, nil, nil} + QuorumTestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, true, 64, 32, big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), false, nil, nil} + QuorumMPSTestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, true, 64, 32, big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), true, nil, nil} ) // TrustedCheckpoint represents a set of post-processed trie roots (CHT and @@ -344,6 +344,7 @@ type ChainConfig struct { IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin switch block (nil = no fork, 0 = already on berlin) + AmsterdamBlock *big.Int `json:"amsterdamBlock,omitempty"` // Amsterdam switch block (nil = no fork, 0 = already on amsterdam) YoloV3Block *big.Int `json:"yoloV3Block,omitempty"` // YOLO v3: Gas repricings TODO @holiman add EIP references EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) @@ -486,7 +487,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v IsQuorum: %v Constantinople: %v TransactionSizeLimit: %v MaxCodeSize: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v Catalyst: %v YOLO v3: %v PrivacyEnhancements: %v PrivacyPrecompile: %v EnableGasPriceBlock: %v Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v IsQuorum: %v Constantinople: %v TransactionSizeLimit: %v MaxCodeSize: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, Amsterdam: %v Catalyst: %v YOLO v3: %v PrivacyEnhancements: %v PrivacyPrecompile: %v EnableGasPriceBlock: %v Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -503,6 +504,7 @@ func (c *ChainConfig) String() string { c.IstanbulBlock, c.MuirGlacierBlock, c.BerlinBlock, + c.AmsterdamBlock, c.CatalystBlock, c.YoloV3Block, c.PrivacyEnhancementsBlock, //Quorum @@ -582,6 +584,11 @@ func (c *ChainConfig) IsBerlin(num *big.Int) bool { return isForked(c.BerlinBlock, num) || isForked(c.YoloV3Block, num) } +// IsAmsterdam returns whether num is either equal to the Amsterdam fork block or greater. +func (c *ChainConfig) IsAmsterdam(num *big.Int) bool { + return isForked(c.AmsterdamBlock, num) +} + // IsCatalyst returns whether num is either equal to the Merge fork block or greater. func (c *ChainConfig) IsCatalyst(num *big.Int) bool { return isForked(c.CatalystBlock, num) @@ -1041,6 +1048,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "istanbulBlock", block: c.IstanbulBlock}, {name: "muirGlacierBlock", block: c.MuirGlacierBlock, optional: true}, {name: "berlinBlock", block: c.BerlinBlock}, + {name: "amsterdamBlock", block: c.AmsterdamBlock}, } { if lastFork.name != "" { // Next one must be higher number @@ -1110,6 +1118,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int, isQuor if isForkIncompatible(c.BerlinBlock, newcfg.BerlinBlock, head) { return newCompatError("Berlin fork block", c.BerlinBlock, newcfg.BerlinBlock) } + if isForkIncompatible(c.AmsterdamBlock, newcfg.AmsterdamBlock, head) { + return newCompatError("Amsterdam fork block", c.AmsterdamBlock, newcfg.AmsterdamBlock) + } if isForkIncompatible(c.YoloV3Block, newcfg.YoloV3Block, head) { return newCompatError("YOLOv3 fork block", c.YoloV3Block, newcfg.YoloV3Block) } @@ -1201,7 +1212,7 @@ type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool - IsBerlin, IsCatalyst bool + IsBerlin, IsAmsterdam, IsCatalyst bool // Quorum IsPrivacyEnhancementsEnabled bool IsPrivacyPrecompile bool @@ -1225,6 +1236,7 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), IsBerlin: c.IsBerlin(num), + IsAmsterdam: c.IsAmsterdam(num), IsCatalyst: c.IsCatalyst(num), // Quorum IsPrivacyEnhancementsEnabled: c.IsPrivacyEnhancementsEnabled(num), diff --git a/params/protocol_params.go b/params/protocol_params.go index 8a38424b6..8c21bc4a8 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -16,7 +16,11 @@ package params -import "math/big" +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) const ( // these are original values from upstream Geth, used in ethash consensus @@ -160,6 +164,10 @@ var ( GenesisDifficulty = big.NewInt(131072) // Difficulty of the Genesis block. MinimumDifficulty = big.NewInt(131072) // The minimum that the difficulty may ever be. DurationLimit = big.NewInt(13) // The decision boundary on the blocktime duration used to determine whether difficulty should go up or not. + + // EIP-7997 - Deterministic deployment factory (keyless CREATE2 factory) + DeterministicFactoryAddress = common.HexToAddress("0x4e59b44847b379578588920cA78FbF26c0B4956C") + DeterministicFactoryCode = common.FromHex("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3") ) func GetMaximumExtraDataSize(isQuorum bool) uint64 { From cd43ac5df7a5c5ec440cd9360501234b02631c27 Mon Sep 17 00:00:00 2001 From: Yoshihito Aso Date: Wed, 8 Jul 2026 17:49:56 +0900 Subject: [PATCH 2/3] Refactor fork order checks for new forks --- params/config.go | 33 +----- params/config_test.go | 235 +++++++++++++++++++++++++++++++----------- 2 files changed, 180 insertions(+), 88 deletions(-) diff --git a/params/config.go b/params/config.go index 7584d8c98..93a3ce0b5 100644 --- a/params/config.go +++ b/params/config.go @@ -1062,6 +1062,9 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "istanbulBlock", block: c.IstanbulBlock}, {name: "muirGlacierBlock", block: c.MuirGlacierBlock, optional: true}, {name: "berlinBlock", block: c.BerlinBlock}, + {name: "pragueBlock", block: c.PragueBlock}, + {name: "osakaBlock", block: c.OsakaBlock}, + {name: "amsterdamBlock", block: c.AmsterdamBlock}, } { if lastFork.name != "" { // Next one must be higher number @@ -1081,36 +1084,6 @@ func (c *ChainConfig) CheckConfigForkOrder() error { lastFork = cur } } - if c.PragueBlock != nil { - if c.BerlinBlock == nil { - return fmt.Errorf("unsupported fork ordering: berlinBlock not enabled, but pragueBlock enabled at %v", - c.PragueBlock) - } - if c.BerlinBlock.Cmp(c.PragueBlock) > 0 { - return fmt.Errorf("unsupported fork ordering: berlinBlock enabled at %v, but pragueBlock enabled at %v", - c.BerlinBlock, c.PragueBlock) - } - } - if c.OsakaBlock != nil { - if c.PragueBlock == nil { - return fmt.Errorf("unsupported fork ordering: pragueBlock not enabled, but osakaBlock enabled at %v", - c.OsakaBlock) - } - if c.PragueBlock.Cmp(c.OsakaBlock) > 0 { - return fmt.Errorf("unsupported fork ordering: pragueBlock enabled at %v, but osakaBlock enabled at %v", - c.PragueBlock, c.OsakaBlock) - } - } - if c.AmsterdamBlock != nil { - if c.OsakaBlock == nil { - return fmt.Errorf("unsupported fork ordering: osakaBlock not enabled, but amsterdamBlock enabled at %v", - c.AmsterdamBlock) - } - if c.OsakaBlock.Cmp(c.AmsterdamBlock) > 0 { - return fmt.Errorf("unsupported fork ordering: osakaBlock enabled at %v, but amsterdamBlock enabled at %v", - c.OsakaBlock, c.AmsterdamBlock) - } - } return nil } diff --git a/params/config_test.go b/params/config_test.go index bafbd64f7..481a7b97c 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -279,6 +279,23 @@ func TestCheckCompatible(t *testing.T) { RewindTo: 9, }, }, + { + stored: &ChainConfig{AmsterdamBlock: big.NewInt(10)}, + new: &ChainConfig{AmsterdamBlock: big.NewInt(20)}, + head: 4, + wantErr: nil, + }, + { + stored: &ChainConfig{AmsterdamBlock: big.NewInt(10)}, + new: &ChainConfig{AmsterdamBlock: big.NewInt(20)}, + head: 30, + wantErr: &ConfigCompatError{ + What: "Amsterdam fork block", + StoredConfig: big.NewInt(10), + NewConfig: big.NewInt(20), + RewindTo: 9, + }, + }, { stored: &ChainConfig{MaxCodeSizeConfig: storedMaxCodeConfig0}, new: &ChainConfig{MaxCodeSizeConfig: nil}, @@ -358,13 +375,13 @@ func TestCheckCompatible(t *testing.T) { } } -func TestCheckConfigForkOrderPragueOsakaAmsterdam(t *testing.T) { - config := func(berlinBlock, pragueBlock, osakaBlock, amsterdamBlock *big.Int) *ChainConfig { +func TestCheckConfigForkOrderPrague(t *testing.T) { + config := func(berlinBlock, pragueBlock *big.Int) *ChainConfig { cfg := *TestChainConfig cfg.BerlinBlock = berlinBlock cfg.PragueBlock = pragueBlock - cfg.OsakaBlock = osakaBlock - cfg.AmsterdamBlock = amsterdamBlock + cfg.OsakaBlock = nil + cfg.AmsterdamBlock = nil return &cfg } @@ -375,44 +392,110 @@ func TestCheckConfigForkOrderPragueOsakaAmsterdam(t *testing.T) { }{ { name: "prague after berlin", - config: config(big.NewInt(10), big.NewInt(20), nil, nil), - }, - { - name: "osaka after prague", - config: config(big.NewInt(10), big.NewInt(20), big.NewInt(30), nil), - }, - { - name: "amsterdam after osaka", - config: config(big.NewInt(10), big.NewInt(20), big.NewInt(30), big.NewInt(40)), + config: config(big.NewInt(10), big.NewInt(20)), }, { name: "prague requires berlin", - config: config(nil, big.NewInt(20), nil, nil), + config: config(nil, big.NewInt(20)), wantErr: "unsupported fork ordering: berlinBlock not enabled, but pragueBlock enabled at 20", }, { name: "prague cannot precede berlin", - config: config(big.NewInt(20), big.NewInt(10), nil, nil), + config: config(big.NewInt(20), big.NewInt(10)), wantErr: "unsupported fork ordering: berlinBlock enabled at 20, but pragueBlock enabled at 10", }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.config.CheckConfigForkOrder() + if tt.wantErr == "" { + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + return + } + if err == nil || err.Error() != tt.wantErr { + t.Fatalf("error mismatch:\nerr: %v\nwant: %v", err, tt.wantErr) + } + }) + } +} + +func TestCheckConfigForkOrderOsaka(t *testing.T) { + config := func(pragueBlock, osakaBlock *big.Int) *ChainConfig { + cfg := *TestChainConfig + cfg.BerlinBlock = big.NewInt(10) + cfg.PragueBlock = pragueBlock + cfg.OsakaBlock = osakaBlock + cfg.AmsterdamBlock = nil + return &cfg + } + + tests := []struct { + name string + config *ChainConfig + wantErr string + }{ + { + name: "osaka after prague", + config: config(big.NewInt(20), big.NewInt(30)), + }, { name: "osaka requires prague", - config: config(big.NewInt(10), nil, big.NewInt(30), nil), + config: config(nil, big.NewInt(30)), wantErr: "unsupported fork ordering: pragueBlock not enabled, but osakaBlock enabled at 30", }, { name: "osaka cannot precede prague", - config: config(big.NewInt(10), big.NewInt(30), big.NewInt(20), nil), + config: config(big.NewInt(30), big.NewInt(20)), wantErr: "unsupported fork ordering: pragueBlock enabled at 30, but osakaBlock enabled at 20", }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.config.CheckConfigForkOrder() + if tt.wantErr == "" { + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + return + } + if err == nil || err.Error() != tt.wantErr { + t.Fatalf("error mismatch:\nerr: %v\nwant: %v", err, tt.wantErr) + } + }) + } +} + +func TestCheckConfigForkOrderAmsterdam(t *testing.T) { + config := func(osakaBlock, amsterdamBlock *big.Int) *ChainConfig { + cfg := *TestChainConfig + cfg.BerlinBlock = big.NewInt(10) + cfg.PragueBlock = big.NewInt(20) + cfg.OsakaBlock = osakaBlock + cfg.AmsterdamBlock = amsterdamBlock + return &cfg + } + + tests := []struct { + name string + config *ChainConfig + wantErr string + }{ + { + name: "amsterdam after osaka", + config: config(big.NewInt(30), big.NewInt(40)), + }, { name: "amsterdam requires osaka", - config: config(big.NewInt(10), big.NewInt(20), nil, big.NewInt(40)), + config: config(nil, big.NewInt(40)), wantErr: "unsupported fork ordering: osakaBlock not enabled, but amsterdamBlock enabled at 40", }, { name: "amsterdam cannot precede osaka", - config: config(big.NewInt(10), big.NewInt(20), big.NewInt(50), big.NewInt(40)), + config: config(big.NewInt(50), big.NewInt(40)), wantErr: "unsupported fork ordering: osakaBlock enabled at 50, but amsterdamBlock enabled at 40", }, } @@ -632,61 +715,103 @@ func TestIsQIP714(t *testing.T) { } } -func TestIsPragueOsakaAndAmsterdam(t *testing.T) { +func TestIsPrague(t *testing.T) { tests := []struct { - name string - config *ChainConfig - block int64 - isPrague bool - isOsaka bool - isAmsterdam bool + name string + config *ChainConfig + block int64 + isPrague bool }{ { - name: "prague requires berlin", - config: &ChainConfig{PragueBlock: big.NewInt(0)}, - block: 0, - isPrague: false, - isOsaka: false, - isAmsterdam: false, + name: "prague requires berlin", + config: &ChainConfig{PragueBlock: big.NewInt(0)}, + block: 0, + isPrague: false, }, { - name: "osaka requires prague", - config: &ChainConfig{BerlinBlock: big.NewInt(0), OsakaBlock: big.NewInt(0)}, - block: 0, - isPrague: false, - isOsaka: false, - isAmsterdam: false, + name: "prague active after berlin and prague blocks", + config: &ChainConfig{BerlinBlock: big.NewInt(0), PragueBlock: big.NewInt(10)}, + block: 10, + isPrague: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + block := big.NewInt(tt.block) + if got := tt.config.IsPrague(block); got != tt.isPrague { + t.Fatalf("IsPrague mismatch: got %v, want %v", got, tt.isPrague) + } + }) + } +} + +func TestIsOsaka(t *testing.T) { + tests := []struct { + name string + config *ChainConfig + block int64 + isOsaka bool + }{ + { + name: "osaka requires prague", + config: &ChainConfig{BerlinBlock: big.NewInt(0), OsakaBlock: big.NewInt(0)}, + block: 0, + isOsaka: false, }, + { + name: "osaka inactive before osaka block", + config: &ChainConfig{BerlinBlock: big.NewInt(0), PragueBlock: big.NewInt(10), OsakaBlock: big.NewInt(20)}, + block: 19, + isOsaka: false, + }, + { + name: "osaka active after prague and osaka blocks", + config: &ChainConfig{BerlinBlock: big.NewInt(0), PragueBlock: big.NewInt(10), OsakaBlock: big.NewInt(20)}, + block: 20, + isOsaka: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + block := big.NewInt(tt.block) + if got := tt.config.IsOsaka(block); got != tt.isOsaka { + t.Fatalf("IsOsaka mismatch: got %v, want %v", got, tt.isOsaka) + } + }) + } +} + +func TestIsAmsterdam(t *testing.T) { + tests := []struct { + name string + config *ChainConfig + block int64 + isAmsterdam bool + }{ { name: "amsterdam requires osaka", config: &ChainConfig{BerlinBlock: big.NewInt(0), PragueBlock: big.NewInt(0), AmsterdamBlock: big.NewInt(0)}, block: 0, - isPrague: true, - isOsaka: false, isAmsterdam: false, }, { - name: "prague active after berlin and prague blocks", - config: &ChainConfig{BerlinBlock: big.NewInt(0), PragueBlock: big.NewInt(10)}, - block: 10, - isPrague: true, - isOsaka: false, + name: "amsterdam requires prague through osaka", + config: &ChainConfig{BerlinBlock: big.NewInt(0), OsakaBlock: big.NewInt(0), AmsterdamBlock: big.NewInt(0)}, + block: 0, isAmsterdam: false, }, { - name: "osaka active after prague and osaka blocks", - config: &ChainConfig{BerlinBlock: big.NewInt(0), PragueBlock: big.NewInt(10), OsakaBlock: big.NewInt(20)}, - block: 20, - isPrague: true, - isOsaka: true, + name: "amsterdam inactive before amsterdam block", + config: &ChainConfig{BerlinBlock: big.NewInt(0), PragueBlock: big.NewInt(10), OsakaBlock: big.NewInt(20), AmsterdamBlock: big.NewInt(30)}, + block: 29, isAmsterdam: false, }, { name: "amsterdam active after osaka and amsterdam blocks", config: &ChainConfig{BerlinBlock: big.NewInt(0), PragueBlock: big.NewInt(10), OsakaBlock: big.NewInt(20), AmsterdamBlock: big.NewInt(30)}, block: 30, - isPrague: true, - isOsaka: true, isAmsterdam: true, }, } @@ -694,12 +819,6 @@ func TestIsPragueOsakaAndAmsterdam(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { block := big.NewInt(tt.block) - if got := tt.config.IsPrague(block); got != tt.isPrague { - t.Fatalf("IsPrague mismatch: got %v, want %v", got, tt.isPrague) - } - if got := tt.config.IsOsaka(block); got != tt.isOsaka { - t.Fatalf("IsOsaka mismatch: got %v, want %v", got, tt.isOsaka) - } if got := tt.config.IsAmsterdam(block); got != tt.isAmsterdam { t.Fatalf("IsAmsterdam mismatch: got %v, want %v", got, tt.isAmsterdam) } From 391aedd009520596daa5fdc13eff91b6f6eb1df1 Mon Sep 17 00:00:00 2001 From: Yoshihito Aso Date: Wed, 8 Jul 2026 18:46:52 +0900 Subject: [PATCH 3/3] Test STUN lookup with mock server Replace the empty STUN endpoint test with a local UDP STUN server that returns a Binding Success response. --- p2p/nat/stun_test.go | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/p2p/nat/stun_test.go b/p2p/nat/stun_test.go index b5f429986..93937fc9a 100644 --- a/p2p/nat/stun_test.go +++ b/p2p/nat/stun_test.go @@ -17,16 +17,42 @@ package nat import ( + "fmt" + "net" "testing" + stunV2 "github.com/pion/stun/v2" + "github.com/pion/stun/v2/stuntest" "github.com/stretchr/testify/assert" ) func TestNatStun(t *testing.T) { - nat, err := newSTUN("") + mappedIP := net.ParseIP("203.0.113.10").To4() + serverAddr, closeServer, err := stuntest.NewUDPServer(t, "udp4", 2048, func(req []byte) ([]byte, error) { + reqMsg := new(stunV2.Message) + if err := stunV2.Decode(req, reqMsg); err != nil { + return nil, err + } + if reqMsg.Type != stunV2.BindingRequest { + return nil, fmt.Errorf("unexpected STUN message type: %v", reqMsg.Type) + } + resp, err := stunV2.Build(reqMsg, stunV2.BindingSuccess, &stunV2.XORMappedAddress{ + IP: mappedIP, + Port: 54321, + }) + if err != nil { + return nil, err + } + return resp.Raw, nil + }) assert.NoError(t, err) - _, err = nat.ExternalIP() + defer closeServer(t) + + nat, err := newSTUN(serverAddr.String()) + assert.NoError(t, err) + ip, err := nat.ExternalIP() assert.NoError(t, err) + assert.Equal(t, mappedIP, ip) } func TestUnreachedNatServer(t *testing.T) {