An xOne node checks a peer before it syncs from it. After the handshake, the node asks the peer for the header at each "required block" height. A peer that serves another hash is on another branch, and the node disconnects it. The same list also stops a chain from being imported, or chosen as a snap pivot, if it passes through the wrong hash.

This post describes what is on main of xOneRS today. The check was added in issue #218 and PR #339.

Why does a node need to check a peer's chain?

A header can be valid and still be on the wrong branch. On Apothem, stale v2.8.2 peers still serve the abandoned branch from before the September 2026 reset (flags.md). Before PR #339, xOneRS had no required-block check. Full sync and live follow accepted any peer whose headers verified. A new or restarted Apothem node could spend its peer slots on that branch (issue #218).

What is a required block?

A required block is a pair of a height and a hash. A peer, or a chain, must have that hash at that height. The list is per network, and an operator can change it.

  • Apothem has one built-in entry: block 83,665,617, the first canonical block after the reset. Its hash is 0x2432ce6433608954ef6eaf2feba8f9a2fe90f5a913401eb07d5fa2f1bb499c3f (required.rs).
  • Mainnet, devnet and net2040 have no built-in entry (PR #339).
  • --eth.requiredblocks takes comma-separated <number>=<0x hash> entries. An operator entry replaces a built-in entry at the same height (flags.md).

To see the merged list, print the config:

xone --apothem dumpconfig
xone --apothem --eth.requiredblocks <number>=<0x hash> dumpconfig

The first command shows the built-in entry. The second adds one entry. A malformed entry such as 5 or 5=0x12 makes the command exit with code 2 (cli.rs).

How does xOne challenge a peer?

The node sends one GetBlockHeaders request for one header per required entry, one at a time. It does this right after the handshake and before the rest of the node hears about the peer (peers/mod.rs).

REFERENCE TABLEScroll to explore all columns →
Answer from the peer Result
One header with the required hash Next entry, or the peer stays
One header with another hash, or more than one header Disconnect, reason 0x03
No answer in 15 seconds Disconnect, reason 0x03
Empty answer (the peer has not reached the height) The peer stays

The empty-answer rule follows geth (eth/handler.go). A peer that is only behind is not on another branch. The session knows the peer's head hash, not its number, so the peer's answer decides whether the height applies (PR #339).

The check works under eth/101, with a reserved request id. It also works under eth/100, where the first header answer is the challenge's (PR #339).

How do I see a refusal?

A refusal writes one log line and adds one to a counter.

peer refused  id=<short id>  reason=required-block  number=<n>  want=<hash>  got=<...>

When the peer never answers, the line ends with no answer in <time> in place of number, want and got. The counter is xone_peers_required_block_refused_total (metrics.rs). See reading an xOne node log for the other lines and what to do with them.

What else does xOne check on a sync path?

The required-block list is one layer. Each layer answers a different question.

REFERENCE TABLEScroll to explore all columns →
Layer Question What happens on a failure
Status handshake Is the peer on our network? Network id, protocol version and genesis hash are checked in that order. The first failure ends the handshake (reason 0x10).
Required blocks Does the peer serve the required hash? Disconnect, reason 0x03.
Header linkage Does each header link to its parent? The headers stage returns SyncError::Link and stores nothing.
Required blocks in the chain Does the run pass through the wrong hash? SyncError::RequiredBlock or InsertError::RequiredBlock, before anything is stored.
Snap pivot Is the pivot's epoch-switch header pinned? --snap.pins, or at least two peers must serve the same header.

Notes:

  • The status message has five fields and no ForkID. A ForkID is never required and never rejected (status62.md).
  • The headers stage compares each header number and parent hash with the one before it, then runs the required-block check (headers.rs).
  • Snap sync checks the required heights before it asks for any state. It covers the pivot's header run and the source's block at each required height below its head (snapsync.rs).
  • --snap.pins is a file with one <number> <0x hash> line per entry. Without it, a peer on another chain is outvoted and logged, not fatal (pivot.md). The flag is recommended beyond test networks. It covers only a snap pivot, so it does not replace the required-block list (issue #218).

For the sync modes themselves, see xOne sync modes. For the networks, see the networks page.

How do other clients describe it?

geth, Besu and Erigon have a flag with the same job. The issue lists the file evidence at the audited commits. The table gives the flag and the source file. It does not compare behaviour in detail.

REFERENCE TABLEScroll to explore all columns →
Client Flag Where the issue found it
geth --eth.requiredblocks cmd/utils/flags.go; eth/handler.go (a requiredBlocks challenge after the handshake); docs
Besu --required-blocks BesuCommand.java; RequiredBlocksPeerValidator.java
Erigon --whitelist cmd/utils/flags.go
geth-XDC (reference pin) --eth.requiredblocks, with the built-in Apothem entry for 83,665,617 cmd/utils/flags.go
xOneRS --eth.requiredblocks, with the same built-in Apothem entry flags.md

What are the limits?

  • The Apothem hash was not checked against a live node when PR #339 merged. The local nodes did not hold block 83,665,617. The hash was copied from the geth-XDC source and the issue (PR #339).
  • Mainnet, devnet and net2040 have no built-in entry. An operator can add one with --eth.requiredblocks.

Frequently asked questions

What does --eth.requiredblocks do in xOne?

It sets heights and hashes that a peer must serve. A peer with another hash at one of those heights is disconnected with reason 0x03 within 15 seconds of the handshake. A chain through another hash is refused before import or pivot choice.

Does an Apothem node need the flag?

No. Apothem has one built-in entry, block 83,665,617. The flag is only needed to add entries or to replace that one.

Is a peer that is behind dropped?

No. An empty answer means the peer has not reached the height, and xOne keeps the peer, as geth does. A peer that never answers is dropped at 15 seconds.

Is --snap.pins the same check?

No. --snap.pins pins the epoch-switch header of a snap pivot. It does not cover full sync or live follow.

Sources