Skip to content

Lighthouse writes non-zero nfd when no next fork is scheduled #8996

@Alleysira

Description

@Alleysira

Description

Hi developers, I am running a multi-client Fulu devnet for testing and noticed that Lighthouse is the only client writing a non-zero nfd ENR field when no next fork is scheduled. All other CL clients (Prysm, Teku, Lodestar) correctly write nfd = 0x00000000 in this scenario.

Differential testing results from a 4-client Kurtosis devnet at Fulu genesis with no BPO fork scheduled:

# lighthouse
nfd=a51ad997  fork_digest=a51ad997  peers=3

# prysm
nfd=00000000  fork_digest=a51ad997  peers=3

# teku
nfd=00000000  fork_digest=a51ad997  peers=3

# lodestar
nfd=00000000  fork_digest=a51ad997  peers=3

Lighthouse sets nfd to the current fork digest (a51ad997) instead of the zero default.

Spec Reference

The Fulu spec specs/fulu/p2p-interface.md defines the nfd ENR field:

If no next fork is scheduled, the nfd entry contains the default value for the type (i.e., the SSZ representation of a zero-filled array).

When next_fork_epoch == FAR_FUTURE_EPOCH (no fork scheduled), the expected nfd value is 0x00000000.

Root Cause

In beacon_node/lighthouse_network/src/service.rs, the nfd value is computed as:

let next_fork_digest = ctx
    .fork_context
    .next_fork_digest()
    .unwrap_or_else(|| ctx.fork_context.current_fork_digest());

When next_fork_digest() returns None (no next fork scheduled), the fallback is current_fork_digest() — a non-zero value. The spec expects the SSZ default (zeros) in this case.

The same value is passed to build_enr in beacon_node/lighthouse_network/src/discovery/enr.rs:

if spec.is_peer_das_scheduled() {
    builder.add_value(PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY, &custody_group_count);
    builder.add_value(NEXT_FORK_DIGEST_ENR_KEY, &next_fork_digest);
}

Present Behaviour

When no next fork is scheduled, Lighthouse writes nfd = current_fork_digest (e.g., 0xa51ad997) to the ENR.

Expected Behaviour

When no next fork is scheduled, Lighthouse should write nfd = 0x00000000 (the SSZ default for ForkDigest).

Suggested Fix

Change the fallback in service.rs from current_fork_digest() to [0u8; 4]:

let next_fork_digest = ctx
    .fork_context
    .next_fork_digest()
    .unwrap_or_default();  // [0, 0, 0, 0] when no next fork

Steps to Reproduce

  1. Launch a multi-client Kurtosis devnet at Fulu genesis with no BPO fork:
# baseline.yaml
participants:
  - el_type: geth
    cl_type: prysm
    el_image: ethereum/client-go:v1.17.1
    cl_image: gcr.io/offchainlabs/prysm/beacon-chain:v7.1.3
    supernode: true
    validator_count: 32
  - el_type: geth
    cl_type: lighthouse
    el_image: ethereum/client-go:v1.17.1
    cl_image: sigp/lighthouse:v8.0.1
    validator_count: 32
  - el_type: geth
    cl_type: teku
    el_image: ethereum/client-go:v1.17.1
    cl_image: consensys/teku:26.3.0
    validator_count: 32
  - el_type: geth
    cl_type: lodestar
    el_image: ethereum/client-go:v1.17.1
    cl_image: chainsafe/lodestar:v1.40.0
    validator_count: 32
network_params:
  electra_fork_epoch: 0
  fulu_fork_epoch: 0
additional_services:
  - dora
kurtosis run github.com/ethpandaops/ethereum-package --args-file baseline.yaml --enclave nfd-test
  1. Query the ENR nfd field from each client's Beacon API:
# For each client:
curl -s http://127.0.0.1:<port>/eth/v1/node/identity | jq -r '.data.enr'
# Decode the ENR and inspect the 'nfd' key

Lighthouse returns nfd=a51ad997 (the current fork digest), while all other clients return nfd=00000000.

Version

  • Lighthouse/v8.0.1 (sigp/lighthouse:v8.0.1)
  • Prysm/v7.1.3 (gcr.io/offchainlabs/prysm/beacon-chain:v7.1.3)
  • Teku/v26.3.0 (consensys/teku:26.3.0)
  • Lodestar/v1.40.0 (chainsafe/lodestar:v1.40.0)

Thanks for your attention!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions