core,params: add fork readiness indicator in logs (#31340)

closes #31310 

This has been requested a few times in the past and I think it is a nice
quality-of-life improvement for users. At a predetermined interval,
there will now be a "Fork ready" log when a future fork is scheduled,
but not yet active.

It can only possibly print after block import, which kinda avoids the
scenario where the client isn't progressing or is syncing and the user
thinks it's "ready" because it sees a ready log.

New output:

```console
INFO [03-08|21:32:57.472] Imported new potential chain segment     number=7 hash=aa24ee..f09e62 blocks=1 txs=0 mgas=0.000 elapsed="874.916µs" mgasps=0.000 snapdiffs=973.00B triediffs=7.05KiB triedirty=0.00B
INFO [03-08|21:32:57.473] Ready for fork activation                fork=Prague date="18 Mar 25 19:29 CET" remaining=237h57m0s timestamp=1,742,322,597
INFO [03-08|21:32:57.475] Chain head was updated                   number=7 hash=aa24ee..f09e62 root=19b0de..8d32f2 elapsed="129.125µs"
```

Easiest way to verify this behavior is to apply this patch and run `geth
--dev --dev.period=12`

```patch
diff --git a/params/config.go b/params/config.go
index 9c7719d901..030c4f80e7 100644
--- a/params/config.go
+++ b/params/config.go
@@ -174,7 +174,7 @@ var (
                ShanghaiTime:            newUint64(0),
                CancunTime:              newUint64(0),
                TerminalTotalDifficulty: big.NewInt(0),
-               PragueTime:              newUint64(0),
+               PragueTime:              newUint64(uint64(time.Now().Add(time.Hour * 300).Unix())),
                BlobScheduleConfig: &BlobScheduleConfig{
                        Cancun: DefaultCancunBlobConfig,
                        Prague: DefaultPragueBlobConfig,
```
This commit is contained in:
lightclient 2025-04-17 02:46:47 -06:00 committed by GitHub
parent 50b5f3125b
commit 13b157a461
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 78 additions and 1 deletions

View file

@ -21,6 +21,7 @@ import (
"errors"
"fmt"
"io"
"math"
"math/big"
"runtime"
"slices"
@ -100,6 +101,10 @@ var (
errInvalidNewChain = errors.New("invalid new chain")
)
var (
forkReadyInterval = 3 * time.Minute
)
const (
bodyCacheLimit = 256
blockCacheLimit = 256
@ -275,6 +280,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
@ -1884,6 +1891,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
@ -1917,6 +1927,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
"root", block.Root())
}
}
stats.ignored += it.remaining()
return witness, it.index, err
}
@ -2514,6 +2525,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 {

View file

@ -909,6 +909,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 {

View file

@ -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",
}