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/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) {
diff --git a/params/config.go b/params/config.go
index 5731d04a0..93a3ce0b5 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), 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), 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), 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), 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), 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), 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, 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, 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, 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, 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
@@ -346,6 +346,7 @@ type ChainConfig struct {
BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin switch block (nil = no fork, 0 = already on berlin)
PragueBlock *big.Int `json:"pragueBlock,omitempty"` // Prague switch block (nil = no fork, 0 = already on prague)
OsakaBlock *big.Int `json:"osakaBlock,omitempty"` // Osaka switch block (nil = no fork, 0 = already on osaka)
+ 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)
@@ -488,7 +489,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, Prague: %v, Osaka: %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, Prague: %v, Osaka: %v, Amsterdam: %v Catalyst: %v YOLO v3: %v PrivacyEnhancements: %v PrivacyPrecompile: %v EnableGasPriceBlock: %v Engine: %v}",
c.ChainID,
c.HomesteadBlock,
c.DAOForkBlock,
@@ -507,6 +508,7 @@ func (c *ChainConfig) String() string {
c.BerlinBlock,
c.PragueBlock,
c.OsakaBlock,
+ c.AmsterdamBlock,
c.CatalystBlock,
c.YoloV3Block,
c.PrivacyEnhancementsBlock, //Quorum
@@ -596,6 +598,11 @@ func (c *ChainConfig) IsOsaka(num *big.Int) bool {
return c.IsPrague(num) && isForked(c.OsakaBlock, num)
}
+// IsAmsterdam returns whether num is either equal to the Amsterdam fork block or greater.
+func (c *ChainConfig) IsAmsterdam(num *big.Int) bool {
+ return c.IsOsaka(num) && 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)
@@ -1055,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
@@ -1074,26 +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)
- }
- }
return nil
}
@@ -1150,6 +1140,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int, isQuor
if isForkIncompatible(c.OsakaBlock, newcfg.OsakaBlock, head) {
return newCompatError("Osaka fork block", c.OsakaBlock, newcfg.OsakaBlock)
}
+ 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)
}
@@ -1241,7 +1234,7 @@ type Rules struct {
ChainID *big.Int
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
- IsBerlin, IsPrague, IsOsaka, IsCatalyst bool
+ IsBerlin, IsPrague, IsOsaka, IsAmsterdam, IsCatalyst bool
// Quorum
IsPrivacyEnhancementsEnabled bool
IsPrivacyPrecompile bool
@@ -1267,6 +1260,7 @@ func (c *ChainConfig) Rules(num *big.Int) Rules {
IsBerlin: c.IsBerlin(num),
IsPrague: c.IsPrague(num),
IsOsaka: c.IsOsaka(num),
+ IsAmsterdam: c.IsAmsterdam(num),
IsCatalyst: c.IsCatalyst(num),
// Quorum
IsPrivacyEnhancementsEnabled: c.IsPrivacyEnhancementsEnabled(num),
diff --git a/params/config_test.go b/params/config_test.go
index 33f1f015e..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,12 +375,13 @@ func TestCheckCompatible(t *testing.T) {
}
}
-func TestCheckConfigForkOrderPragueOsaka(t *testing.T) {
- config := func(berlinBlock, pragueBlock, osakaBlock *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.OsakaBlock = nil
+ cfg.AmsterdamBlock = nil
return &cfg
}
@@ -374,30 +392,63 @@ func TestCheckConfigForkOrderPragueOsaka(t *testing.T) {
}{
{
name: "prague after berlin",
- config: config(big.NewInt(10), big.NewInt(20), nil),
- },
- {
- name: "osaka after prague",
- config: config(big.NewInt(10), big.NewInt(20), big.NewInt(30)),
+ config: config(big.NewInt(10), big.NewInt(20)),
},
{
name: "prague requires berlin",
- config: config(nil, big.NewInt(20), 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),
+ 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)),
+ 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)),
+ config: config(big.NewInt(30), big.NewInt(20)),
wantErr: "unsupported fork ordering: pragueBlock enabled at 30, but osakaBlock enabled at 20",
},
}
@@ -418,6 +469,53 @@ func TestCheckConfigForkOrderPragueOsaka(t *testing.T) {
}
}
+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(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(50), big.NewInt(40)),
+ wantErr: "unsupported fork ordering: osakaBlock enabled at 50, but amsterdamBlock enabled at 40",
+ },
+ }
+
+ 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 TestCheckTransitionsData(t *testing.T) {
type test struct {
stored *ChainConfig
@@ -617,41 +715,24 @@ func TestIsQIP714(t *testing.T) {
}
}
-func TestIsPragueAndOsaka(t *testing.T) {
+func TestIsPrague(t *testing.T) {
tests := []struct {
name string
config *ChainConfig
block int64
isPrague bool
- isOsaka bool
}{
{
name: "prague requires berlin",
config: &ChainConfig{PragueBlock: big.NewInt(0)},
block: 0,
isPrague: false,
- isOsaka: false,
- },
- {
- name: "osaka requires prague",
- config: &ChainConfig{BerlinBlock: big.NewInt(0), OsakaBlock: big.NewInt(0)},
- block: 0,
- isPrague: false,
- isOsaka: 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: "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,
},
}
@@ -661,6 +742,40 @@ func TestIsPragueAndOsaka(t *testing.T) {
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)
}
@@ -668,6 +783,49 @@ func TestIsPragueAndOsaka(t *testing.T) {
}
}
+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,
+ isAmsterdam: 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: "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,
+ isAmsterdam: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ block := big.NewInt(tt.block)
+ if got := tt.config.IsAmsterdam(block); got != tt.isAmsterdam {
+ t.Fatalf("IsAmsterdam mismatch: got %v, want %v", got, tt.isAmsterdam)
+ }
+ })
+ }
+}
+
func TestIsPrivacyEnhancementsEnabled(t *testing.T) {
type test struct {
config *ChainConfig
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 {