Repository: ethereum/go-ethereum (language: go)

Issue:
Add fork readiness indicator to logs
Many of the CLs print a log periodically when they have been upgraded to a fork, but the fork isn't active. It seems that users appreciate this peace of mind seeing they client is "ready". Sure we print the fork info when geth is started up and they can always review that info, but this seems like a nice feature to add.

something like

```
INFO Ready for Prague
```

every 3-5 mins?


Discussion / hints:

Fix patch:
diff --git a/core/blockchain.go b/core/blockchain.go
index b98c2d43aa83..40c750b8b311 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -21,6 +21,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"math"
 	"math/big"
 	"runtime"
 	"slices"
@@ -98,6 +99,10 @@ var (
 	errInvalidNewChain      = errors.New("invalid new chain")
 )
 
+var (
+	forkReadyInterval = 3 * time.Minute
+)
+
 const (
 	bodyCacheLimit     = 256
 	blockCacheLimit    = 256
@@ -268,6 +273,8 @@ type BlockChain struct {
 	processor  Processor // Block transaction processor interface
 	vmConfig   vm.Config
 	logger     *tracing.Hooks
+
+	lastForkReadyAlert time.Time // Last time there was a fork readiness print out
 }
 
 // NewBlockChain returns a fully initialised block chain using information
@@ -1825,6 +1832,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
 		trieDiffNodes, trieBufNodes, _ := bc.triedb.Size()
 		stats.report(chain, it.index, snapDiffItems, snapBufItems, trieDiffNodes, trieBufNodes, setHead)
 
+		// Print confirmation that a future fork is scheduled, but not yet active.
+		bc.logForkReadiness(block)
+
 		if !setHead {
 			// After merge we expect few side chains. Simply count
 			// all blocks the CL gives us for GC processing time
@@ -1858,6 +1868,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
 				"root", block.Root())
 		}
 	}
+
 	stats.ignored += it.remaining()
 	return witness, it.index, err
 }
@@ -2455,6 +2466,23 @@ func (bc *BlockChain) reportBlock(block *types.Block, res *ProcessResult, err er
 	log.Error(summarizeBadBlock(block, receipts, bc.Config(), err))
 }
 
+// logForkReadiness will write a log when a future fork is scheduled, but not
+// active. This is useful so operators know their client is ready for the fork.
+func (bc *BlockChain) logForkReadiness(block *types.Block) {
+	c := bc.Config()
+	current, last := c.LatestFork(block.Time()), c.LatestFork(math.MaxUint64)
+	t := c.Timestamp(last)
+	if t == nil {
+		return
+	}
+	at := time.Unix(int64(*t), 0)
+	if current < last && time.Now().After(bc.lastForkReadyAlert.Add(forkReadyInterval)) {
+		log.Info("Ready for fork activation", "fork", last, "date", at.Format(time.RFC822),
+			"remaining", time.Until(at).Round(time.Second), "timestamp", at.Unix())
+		bc.lastForkReadyAlert = time.Now()
+	}
+}
+
 // summarizeBadBlock returns a string summarizing the bad block and other
 // relevant information.
 func summarizeBadBlock(block *types.Block, receipts []*types.Receipt, config *params.ChainConfig, err error) string {
diff --git a/params/config.go b/params/config.go
index 593c70b1390d..8ba247c447e8 100644
--- a/params/config.go
+++ b/params/config.go
@@ -875,6 +875,23 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork {
 	}
 }
 
+// Timestamp returns the timestamp associated with the fork or returns nil if
+// the fork isn't defined or isn't a time-based fork.
+func (c *ChainConfig) Timestamp(fork forks.Fork) *uint64 {
+	switch {
+	case fork == forks.Osaka:
+		return c.OsakaTime
+	case fork == forks.Prague:
+		return c.PragueTime
+	case fork == forks.Cancun:
+		return c.CancunTime
+	case fork == forks.Shanghai:
+		return c.ShanghaiTime
+	default:
+		return nil
+	}
+}
+
 // isForkBlockIncompatible returns true if a fork scheduled at block s1 cannot be
 // rescheduled to block s2 because head is already past the fork.
 func isForkBlockIncompatible(s1, s2, head *big.Int) bool {
diff --git a/params/forks/forks.go b/params/forks/forks.go
index 2d44e13b04d8..5d0123b7344f 100644
--- a/params/forks/forks.go
+++ b/params/forks/forks.go
@@ -20,7 +20,7 @@ package forks
 type Fork int
 
 const (
-	Frontier = iota
+	Frontier Fork = iota
 	FrontierThawing
 	Homestead
 	DAO
@@ -41,3 +41,35 @@ const (
 	Prague
 	Osaka
 )
+
+// String implements fmt.Stringer.
+func (f Fork) String() string {
+	s, ok := forkToString[f]
+	if !ok {
+		return "Unknown fork"
+	}
+	return s
+}
+
+var forkToString = map[Fork]string{
+	Frontier:         "Frontier",
+	FrontierThawing:  "Frontier Thawing",
+	Homestead:        "Homestead",
+	DAO:              "DAO",
+	TangerineWhistle: "Tangerine Whistle",
+	SpuriousDragon:   "Spurious Dragon",
+	Byzantium:        "Byzantium",
+	Constantinople:   "Constantinople",
+	Petersburg:       "Petersburg",
+	Istanbul:         "Istanbul",
+	MuirGlacier:      "Muir Glacier",
+	Berlin:           "Berlin",
+	London:           "London",
+	ArrowGlacier:     "Arrow Glacier",
+	GrayGlacier:      "Gray Glacier",
+	Paris:            "Paris",
+	Shanghai:         "Shanghai",
+	Cancun:           "Cancun",
+	Prague:           "Prague",
+	Osaka:            "Osaka",
+}


Test patch: