diff --git a/params/config_etc.go b/params/config_etc.go
index e04f0ab7a..21569677b 100644
--- a/params/config_etc.go
+++ b/params/config_etc.go
@@ -17,6 +17,7 @@
package params
import (
+ "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
@@ -54,7 +55,8 @@ var (
CancunTime: nil,
PragueTime: nil,
OsakaTime: nil,
- VerkleTime: nil,
+ AmsterdamTime: nil,
+ UBTTime: nil,
TerminalTotalDifficulty: nil, // ETC is perpetual PoW, no TTD
// ETC-specific ECIPs
ECIP1017Block: big.NewInt(5_000_000), // Gotham monetary policy
@@ -84,8 +86,8 @@ var (
PetersburgBlock: big.NewInt(301_243), // Mordor Agharta
IstanbulBlock: big.NewInt(999_983), // Mordor Phoenix
MuirGlacierBlock: nil, // No Muir Glacier
- BerlinBlock: big.NewInt(2_520_000), // Mordor Magneto
- LondonBlock: big.NewInt(3_985_893), // Mordor Mystique (WITHOUT EIP-1559)
+ BerlinBlock: big.NewInt(3_985_893), // Mordor Magneto
+ LondonBlock: big.NewInt(5_520_000), // Mordor Mystique (WITHOUT EIP-1559)
ArrowGlacierBlock: nil,
GrayGlacierBlock: nil,
MergeNetsplitBlock: nil,
@@ -94,11 +96,12 @@ var (
CancunTime: nil,
PragueTime: nil,
OsakaTime: nil,
- VerkleTime: nil,
+ AmsterdamTime: nil,
+ UBTTime: nil,
TerminalTotalDifficulty: nil, // Perpetual PoW
// ETC-specific ECIPs
- ECIP1017Block: nil, // From genesis (not a fork point)
- ECIP1041Block: nil, // Mordor never had bomb
+ ECIP1017Block: big.NewInt(0), // Active from genesis
+ ECIP1041Block: big.NewInt(0), // Bomb disposed from genesis
ECIP1099Block: big.NewInt(2_520_000), // Etchash from Magneto
SpiralBlock: big.NewInt(9_957_000), // Mordor Spiral
ECIP1017EraRounds: big.NewInt(2_000_000), // Era length: 2M blocks
@@ -126,6 +129,55 @@ func (c *ChainConfig) IsPow() bool {
return c.Ethash != nil && c.TerminalTotalDifficulty == nil
}
+// describePoWForks returns the human-readable hard-fork section for a
+// perpetual PoW chain. Used by ChainConfig.Description for the --classic /
+// --mordor banner, where merge/post-merge sections do not apply and forks
+// carry their ETC names. Only non-nil fields are printed, so the helper
+// works for both ETC mainnet and Mordor (which omits a few ECIPs).
+func (c *ChainConfig) describePoWForks() string {
+ s := "Hard forks (block based):\n"
+ if c.HomesteadBlock != nil {
+ s += fmt.Sprintf(" - Homestead: #%-8v\n", c.HomesteadBlock)
+ }
+ if c.EIP150Block != nil {
+ s += fmt.Sprintf(" - Tangerine Whistle: #%-8v\n", c.EIP150Block)
+ }
+ if c.ECIP1010Transition != nil {
+ s += fmt.Sprintf(" - DieHard: #%-8v\n", c.ECIP1010Transition)
+ }
+ if c.EIP155Block != nil {
+ s += fmt.Sprintf(" - Spurious Dragon: #%-8v\n", c.EIP155Block)
+ }
+ if c.ECIP1017Block != nil {
+ s += fmt.Sprintf(" - Gotham: #%-8v\n", c.ECIP1017Block)
+ }
+ if c.ECIP1041Block != nil {
+ s += fmt.Sprintf(" - Bomb disposal: #%-8v\n", c.ECIP1041Block)
+ }
+ if c.ByzantiumBlock != nil {
+ s += fmt.Sprintf(" - Atlantis: #%-8v\n", c.ByzantiumBlock)
+ }
+ if c.ConstantinopleBlock != nil {
+ s += fmt.Sprintf(" - Agharta: #%-8v\n", c.ConstantinopleBlock)
+ }
+ if c.IstanbulBlock != nil {
+ s += fmt.Sprintf(" - Phoenix: #%-8v\n", c.IstanbulBlock)
+ }
+ if c.ECIP1099Block != nil {
+ s += fmt.Sprintf(" - Etchash: #%-8v\n", c.ECIP1099Block)
+ }
+ if c.BerlinBlock != nil {
+ s += fmt.Sprintf(" - Magneto: #%-8v\n", c.BerlinBlock)
+ }
+ if c.LondonBlock != nil {
+ s += fmt.Sprintf(" - Mystique: #%-8v\n", c.LondonBlock)
+ }
+ if c.SpiralBlock != nil {
+ s += fmt.Sprintf(" - Spiral: #%-8v\n", c.SpiralBlock)
+ }
+ return s
+}
+
// IsSpiral returns whether num is either equal to the Spiral fork block or greater.
func (c *ChainConfig) IsSpiral(num *big.Int) bool {
return isBlockForked(c.SpiralBlock, num)
@@ -185,6 +237,92 @@ func (c *ChainConfig) isEIP160(num *big.Int) bool {
return false
}
+// checkCompatibleETC guards the ETC-specific fork blocks against incompatible changes.
+// Upstream's checkCompatible enumerates its own forks but knows nothing about the forks
+// getc adds. Those *Block fields enter the fork ID via gatherForks' reflection, so a
+// changed Etchash/Spiral/rewards block on an already-advanced DB would otherwise be
+// accepted silently (SetupGenesisBlock rewrites the stored config when CheckCompatible
+// returns no error).
+//
+// Enumerated on purpose: go-ethereum has always kept checkCompatible enumerated (only the
+// fork ID was moved to reflection), so this mirrors upstream's style and stays rebase-
+// friendly. TestETCForkCompatibilityCoverage enforces that every fork-ID *Block is guarded
+// here, giving us the fork-ID↔compatibility symmetry core-geth gets from its reflective
+// confp system — without importing reflection into production code.
+//
+// MESS (ECBP1100Transition / ECBP1100DeactivateTransition) is intentionally excluded, just
+// like it is excluded from the fork ID: it is a fork-choice/reorg policy, not a block-
+// validity rule, so changing it cannot retroactively split consensus.
+func (c *ChainConfig) checkCompatibleETC(newcfg *ChainConfig, headNumber *big.Int) *ConfigCompatError {
+ if isForkBlockIncompatible(c.ECIP1017Block, newcfg.ECIP1017Block, headNumber) {
+ return newBlockCompatError("ECIP-1017 (Gotham) fork block", c.ECIP1017Block, newcfg.ECIP1017Block)
+ }
+ if isForkBlockIncompatible(c.ECIP1041Block, newcfg.ECIP1041Block, headNumber) {
+ return newBlockCompatError("ECIP-1041 (bomb disposal) fork block", c.ECIP1041Block, newcfg.ECIP1041Block)
+ }
+ if isForkBlockIncompatible(c.ECIP1099Block, newcfg.ECIP1099Block, headNumber) {
+ return newBlockCompatError("ECIP-1099 (Etchash) fork block", c.ECIP1099Block, newcfg.ECIP1099Block)
+ }
+ if isForkBlockIncompatible(c.SpiralBlock, newcfg.SpiralBlock, headNumber) {
+ return newBlockCompatError("Spiral fork block", c.SpiralBlock, newcfg.SpiralBlock)
+ }
+ if isForkBlockIncompatible(c.ECIP1010Transition, newcfg.ECIP1010Transition, headNumber) {
+ return newBlockCompatError("ECIP-1010 (DieHard) transition", c.ECIP1010Transition, newcfg.ECIP1010Transition)
+ }
+ // ECIP1017EraRounds / ECIP1010Length are parameters (era length, pause duration), not
+ // activation blocks: changing them rewrites the reward schedule / bomb pause retroactively.
+ // They don't enter the fork ID, so require equality while their fork is active.
+ if c.IsECIP1017(headNumber) && !configBlockEqual(c.ECIP1017EraRounds, newcfg.ECIP1017EraRounds) {
+ return newBlockCompatError("ECIP-1017 era rounds", c.ECIP1017EraRounds, newcfg.ECIP1017EraRounds)
+ }
+ if c.IsECIP1010(headNumber) && !configBlockEqual(c.ECIP1010Length, newcfg.ECIP1010Length) {
+ return newBlockCompatError("ECIP-1010 pause length", c.ECIP1010Length, newcfg.ECIP1010Length)
+ }
+ return nil
+}
+
+// checkConfigForkOrderETC checks the ordering of the ETC-specific fork blocks. Upstream's
+// CheckConfigForkOrder validates the Ethereum ordering of the shared forks, but knows nothing
+// about the ETC forks getc adds (Gotham/DieHard/Etchash/Spiral), nor about where they sit
+// relative to the shared ones. This validates the canonical ETC sequence so an out-of-order
+// config (e.g. Spiral before Mystique, or Etchash before Phoenix) is rejected.
+//
+// A few shared forks are included as anchors so the ETC forks are ordered relative to them.
+// Every entry is optional (skipped if nil): only ordering is enforced, not presence — Classic
+// and Mordor enable different subsets and place some forks at the same block (e.g. Mordor has
+// Magneto == Etchash), which equality tolerates.
+func (c *ChainConfig) checkConfigForkOrderETC() error {
+ type fork struct {
+ name string
+ block *big.Int
+ }
+ var last fork
+ for _, cur := range []fork{
+ {"eip155Block", c.EIP155Block},
+ {"ecip1010Transition (DieHard)", c.ECIP1010Transition},
+ {"ecip1017Block (Gotham)", c.ECIP1017Block},
+ {"ecip1041Block (bomb disposal)", c.ECIP1041Block},
+ // ETC delayed EIP-158 to Atlantis (8.772M on Classic), unlike Ethereum's block 3M.
+ {"eip158Block (Atlantis)", c.EIP158Block},
+ {"byzantiumBlock (Atlantis)", c.ByzantiumBlock},
+ {"istanbulBlock (Phoenix)", c.IstanbulBlock},
+ {"ecip1099Block (Etchash)", c.ECIP1099Block},
+ {"berlinBlock (Magneto)", c.BerlinBlock},
+ {"londonBlock (Mystique)", c.LondonBlock},
+ {"spiralBlock (Spiral)", c.SpiralBlock},
+ } {
+ if cur.block == nil {
+ continue // optional: presence not required, only ordering
+ }
+ if last.block != nil && last.block.Cmp(cur.block) > 0 {
+ return fmt.Errorf("unsupported ETC fork ordering: %s enabled at block %v, but %s enabled at block %v",
+ last.name, last.block, cur.name, cur.block)
+ }
+ last = cur
+ }
+ return nil
+}
+
func init() {
// Register ETC networks in NetworkNames
NetworkNames[ClassicChainConfig.ChainID.String()] = "classic"