Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b6f824e
Bound discv4 handler node map to prevent unbounded growth
damilolaedwards Jul 6, 2026
d9a264f
Guard discv4/discv5 protocol pointers on the generic node
damilolaedwards Jul 6, 2026
36ec1ff
Deliver discv4 responses without blocking the dispatch goroutine
damilolaedwards Jul 7, 2026
e79f338
Bound accumulation of pending NEIGHBORS responses
damilolaedwards Jul 7, 2026
ed6d31a
Fix startup session/pool bugs: set node on initiator sessions, lock L…
MysticRyuujin Jul 16, 2026
e10a691
Re-admit demoted nodes to the active pool on traffic
MysticRyuujin Jul 16, 2026
1a5f008
Bump the dependencies group across 1 directory with 6 updates
dependabot[bot] Jul 20, 2026
59e3db6
fix(discv5): install refreshed peer ENRs
MysticRyuujin Jul 22, 2026
e666739
Merge PR #35: Guard discv4/discv5 protocol pointers on the generic node
MysticRyuujin Jul 27, 2026
0bdb4cb
Merge PR #37: Deliver discv4 responses without blocking the dispatch …
MysticRyuujin Jul 27, 2026
1ee5cb8
Merge PR #39: Fix startup session races and active-pool re-admission
MysticRyuujin Jul 27, 2026
18ed3b6
Merge PR #41: Fix discv5 ENR refresh installation
MysticRyuujin Jul 27, 2026
6500ec9
Merge PR #40: Bump the dependencies group across 1 directory with 6 u…
MysticRyuujin Jul 27, 2026
b519e8f
Merge PR #34: Bound discv4 handler node map to prevent unbounded growth
MysticRyuujin Jul 27, 2026
8adc859
Merge PR #38: Bound accumulation of pending NEIGHBORS responses
MysticRyuujin Jul 27, 2026
a9d4df3
fix(discv4): address review feedback on #34 and #38
MysticRyuujin Jul 27, 2026
2e26bb2
fix: race and cap fixes from the develop-vs-master review
MysticRyuujin Jul 27, 2026
bc8006b
fix(fork): correct fork-id/digest admission and publication
MysticRyuujin Jul 27, 2026
58f1fc7
fix(discovery): never dial our own identities
MysticRyuujin Jul 27, 2026
be20e05
fix(webui): report real discovery stats instead of hardcoded zeros
MysticRyuujin Jul 27, 2026
c9e6693
fix: update discv4 ENR in place and honor --cl-genesis-time
MysticRyuujin Jul 27, 2026
9544615
fix: distinguish cross-layer from wrong-fork rejections, surface EL a…
MysticRyuujin Jul 27, 2026
3dafc9c
fix(webui): populate the grace-period digest card
MysticRyuujin Jul 27, 2026
2d58439
fix(fork): spec-correct eth2 encoding and post-fork record refresh
MysticRyuujin Jul 27, 2026
470d0cc
fix: refresh known v4 records, tighten layer gating, unnest EL stats
MysticRyuujin Jul 27, 2026
71d9eca
fix(lookup): admit newer relayed records for known nodes
MysticRyuujin Jul 27, 2026
461a23f
fix(discv5): accept a record-less handshake, bind key to claimed ID
MysticRyuujin Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 101 additions & 124 deletions bootnode/clconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ func LoadConfig(path string) (*Config, error) {

cfg.rawConfig = rawConfig

// GetBlobParamsForEpoch's early break and addBPOForks' naming both assume
// an ascending schedule; the YAML carries no ordering guarantee.
sort.SliceStable(cfg.BlobSchedule, func(i, j int) bool {
return cfg.BlobSchedule[i].Epoch < cfg.BlobSchedule[j].Epoch
})

// Extract fork data dynamically from the map
if err := cfg.extractForkData(rawConfig); err != nil {
return nil, fmt.Errorf("failed to extract fork data: %w", err)
Expand Down Expand Up @@ -524,28 +530,52 @@ func (c *Config) GetForkDigestForEpoch(epoch uint64) ForkDigest {
return c.GetForkDigest(forkVersion, blobParams)
}

// GetCurrentForkDigest returns the fork digest for the current epoch.
//
// Calculates the current epoch based on genesis time and returns the appropriate fork digest.
func (c *Config) GetCurrentForkDigest() ForkDigest {
// Get genesis time
// currentEpochNow computes the current epoch from genesis time and wall
// clock. The second return is false when no genesis time is configured.
func (c *Config) currentEpochNow() (uint64, bool) {
genesisTime := c.GetGenesisTime()
if genesisTime == 0 {
// No genesis time, fall back to latest fork with realistic epoch
return c.getFallbackForkDigest()
return 0, false
}

// Calculate current epoch
currentTime := uint64(time.Now().Unix())
slotsPerEpoch := c.GetSlotsPerEpoch()
secondsPerSlot := c.SecondsPerSlot
if secondsPerSlot == 0 {
secondsPerSlot = 12 // Default
secondsPerSlot = 12
}
return uint64(GetCurrentEpoch(genesisTime, currentTime, secondsPerSlot, c.GetSlotsPerEpoch())), true
}

currentEpoch := uint64(GetCurrentEpoch(genesisTime, currentTime, secondsPerSlot, slotsPerEpoch))
// forkBoundaryEpochs returns every epoch at which the wire digest can change:
// genesis, each registered fork (including BPO pseudo-forks), and each blob
// schedule boundary. Sorted ascending, deduplicated.
func (c *Config) forkBoundaryEpochs() []uint64 {
seen := map[uint64]bool{0: true}
epochs := []uint64{0}
add := func(epoch uint64) {
if epoch != math.MaxUint64 && !seen[epoch] {
seen[epoch] = true
epochs = append(epochs, epoch)
}
}
for _, fork := range c.getForks() {
add(fork.epoch)
}
for _, entry := range c.BlobSchedule {
add(entry.Epoch)
}
sort.Slice(epochs, func(i, j int) bool { return epochs[i] < epochs[j] })
return epochs
}

// Return fork digest for current epoch
// GetCurrentForkDigest returns the fork digest for the current epoch.
//
// Calculates the current epoch based on genesis time and returns the appropriate fork digest.
func (c *Config) GetCurrentForkDigest() ForkDigest {
currentEpoch, ok := c.currentEpochNow()
if !ok {
// No genesis time, fall back to latest fork with realistic epoch
return c.getFallbackForkDigest()
}
return c.GetForkDigestForEpoch(currentEpoch)
}

Expand All @@ -554,86 +584,44 @@ func (c *Config) GetGenesisForkDigest() ForkDigest {
return c.GetForkDigest(c.genesisForkVersion, nil)
}

// GetPreviousForkDigest returns the fork digest for the previous fork before the current one.
// Returns the genesis fork digest if there is no previous fork.
func (c *Config) GetPreviousForkDigest() ForkDigest {
// Get genesis time
genesisTime := c.GetGenesisTime()
if genesisTime == 0 {
// No genesis time, return genesis fork digest
return c.GetGenesisForkDigest()
}

// Calculate current epoch
currentTime := uint64(time.Now().Unix())
slotsPerEpoch := c.GetSlotsPerEpoch()
secondsPerSlot := c.SecondsPerSlot
if secondsPerSlot == 0 {
secondsPerSlot = 12
// previousBoundaryEpoch returns the boundary epoch immediately before the
// currently active one, and whether such a boundary exists.
func (c *Config) previousBoundaryEpoch() (uint64, bool) {
currentEpoch, ok := c.currentEpochNow()
if !ok {
return 0, false
}
currentEpoch := uint64(GetCurrentEpoch(genesisTime, currentTime, secondsPerSlot, slotsPerEpoch))

// Find the fork before the current one by iterating through forks in forward order
forks := c.getForks()
var currentFork *forkDefinition
var previousFork *forkDefinition

for i := 0; i < len(forks); i++ {
fork := forks[i]
if currentEpoch >= fork.epoch {
// This fork is active, remember it as current
previousFork = currentFork // The last current becomes previous
currentFork = &forks[i] // This is now current
var passed []uint64
for _, epoch := range c.forkBoundaryEpochs() {
if epoch > currentEpoch {
break
}
passed = append(passed, epoch)
}

// Return the previous fork if it exists
if previousFork != nil {
return c.GetForkDigest(previousFork.parsedVersion, nil)
if len(passed) < 2 {
return 0, false
}
return passed[len(passed)-2], true
}

// No previous fork, return genesis
return c.GetGenesisForkDigest()
// GetPreviousForkDigest returns the wire digest that was current before the
// active boundary, computed on the same enumeration as GetAllForkDigests.
// Returns the genesis fork digest if there is no previous boundary.
func (c *Config) GetPreviousForkDigest() ForkDigest {
epoch, ok := c.previousBoundaryEpoch()
if !ok {
return c.GetGenesisForkDigest()
}
return c.GetForkDigestForEpoch(epoch)
}

// GetPreviousForkName returns the name of the previous fork before the current one.
func (c *Config) GetPreviousForkName() string {
// Get genesis time
genesisTime := c.GetGenesisTime()
if genesisTime == 0 {
epoch, ok := c.previousBoundaryEpoch()
if !ok {
return "Phase0"
}

// Calculate current epoch
currentTime := uint64(time.Now().Unix())
slotsPerEpoch := c.GetSlotsPerEpoch()
secondsPerSlot := c.SecondsPerSlot
if secondsPerSlot == 0 {
secondsPerSlot = 12
}
currentEpoch := uint64(GetCurrentEpoch(genesisTime, currentTime, secondsPerSlot, slotsPerEpoch))

// Find the fork before the current one by iterating through forks in forward order
forks := c.getForks()
var currentFork *forkDefinition
var previousFork *forkDefinition

for i := 0; i < len(forks); i++ {
fork := forks[i]
if currentEpoch >= fork.epoch {
// This fork is active, remember it as current
previousFork = currentFork // The last current becomes previous
currentFork = &forks[i] // This is now current
}
}

// Return the previous fork name if it exists
if previousFork != nil {
return previousFork.name
}

// No previous fork, return Phase0
return "Phase0"
return c.GetForkNameAtEpoch(epoch)
}

// getFallbackForkDigest returns the latest fork with a realistic epoch.
Expand Down Expand Up @@ -665,57 +653,43 @@ type ForkDigestInfo struct {
ForkVersion [4]byte
}

// GetAllForkDigests returns all possible fork digests for this config.
//
// This is useful for creating filters that accept nodes from multiple forks.
// Note: For Fulu+ forks with blob schedules, this returns multiple digests per fork.
// GetAllForkDigests returns every fork digest that can appear on the wire
// for this config: one per boundary epoch, computed through the same
// GetForkDigestForEpoch path that produces the live current digest. Digests
// for same-epoch intermediate forks (never current on the wire) are
// intentionally not included.
func (c *Config) GetAllForkDigests() []ForkDigest {
var digests []ForkDigest

// Genesis (epoch 0) - use genesis fork version
digests = append(digests, c.GetForkDigest(c.genesisForkVersion, nil))

// All forks (including BPOs) - use their specific fork versions
for _, fork := range c.getForks() {
if fork.epoch != math.MaxUint64 {
// Get blob parameters active at this fork's epoch (if any)
blobParams := c.GetBlobParamsForEpoch(fork.epoch)
digests = append(digests, c.GetForkDigest(fork.parsedVersion, blobParams))
seen := make(map[ForkDigest]bool)
for _, epoch := range c.forkBoundaryEpochs() {
digest := c.GetForkDigestForEpoch(epoch)
if !seen[digest] {
seen[digest] = true
digests = append(digests, digest)
}
}

return digests
}

// GetAllForkDigestInfos returns all fork digests with their metadata.
// GetAllForkDigestInfos returns all wire-valid fork digests with their
// metadata, on the same boundary enumeration as GetAllForkDigests.
func (c *Config) GetAllForkDigestInfos() []ForkDigestInfo {
var infos []ForkDigestInfo

// Add Genesis (epoch 0)
infos = append(infos, ForkDigestInfo{
Digest: c.GetForkDigest(c.genesisForkVersion, nil),
Name: "Phase0/Genesis",
Epoch: 0,
BlobParams: nil,
ForkVersion: c.genesisForkVersion,
})

// Add all forks (including BPOs) - they're already in the correct order
for _, fork := range c.getForks() {
if fork.epoch != math.MaxUint64 {
// Get blob parameters active at this fork's epoch (if any)
blobParams := c.GetBlobParamsForEpoch(fork.epoch)

infos = append(infos, ForkDigestInfo{
Digest: c.GetForkDigest(fork.parsedVersion, blobParams),
Name: fork.name,
Epoch: fork.epoch,
BlobParams: blobParams,
ForkVersion: fork.parsedVersion,
})
seen := make(map[ForkDigest]bool)
for _, epoch := range c.forkBoundaryEpochs() {
digest := c.GetForkDigestForEpoch(epoch)
if seen[digest] {
continue
}
seen[digest] = true
infos = append(infos, ForkDigestInfo{
Digest: digest,
Name: c.GetForkNameAtEpoch(epoch),
Epoch: epoch,
BlobParams: c.GetBlobParamsForEpoch(epoch),
ForkVersion: c.GetForkVersionAtEpoch(epoch),
})
}

return infos
}

Expand Down Expand Up @@ -825,8 +799,11 @@ func EncodeETH2Field(currentDigest ForkDigest, nextForkVersion [4]byte, nextFork
// Next fork version (bytes 4-7)
copy(field[4:8], nextForkVersion[:])

// Next fork epoch (bytes 8-15, big endian)
binary.BigEndian.PutUint64(field[8:16], nextForkEpoch)
// Bytes 8-15: the eth2 entry is an SSZ ENRForkID, so the epoch is a
// little-endian uint64. Big-endian reads identically when the epoch is
// FAR_FUTURE_EPOCH, which is why this went unnoticed until a fork was
// actually scheduled.
binary.LittleEndian.PutUint64(field[8:16], nextForkEpoch)

return field
}
Loading