Skip to content

--set-head <N> overshoots to N-1 and fatally aborts: 'can't rollback to N which is greater than current N-1' #2465

Description

@AnilChinchawale

Summary

The --set-head <N> startup flag (env XDC_SET_HEAD, "Rollback chain to block number") overshoots by one block and then fatally aborts startup when N is at/near the node's current committed head. Instead of rewinding to N and continuing, it rewinds to N, N-1, N-2, ... and crashes with:

Fatal: Failed to register the Ethereum service: can't rollback to <N> which is greater than current <N-1>

This makes the flag unusable for its primary purpose (dropping a small uncommitted tail during recovery), and leaves the node down rather than at head N.

Environment

  • Client: XDC v2.7.0-devnet (XDC-v270)
  • Datadir restored from a snapshot whose committed head was block 14835 (leveldb contained a non-canonical tail up to ~14968; no ancient freezer)

Reproduction

XDC-v270 --allow-builtin-config-override --set-head 14835 \
  --datadir <DIR> --networkid 5151 --syncmode full --gcmode full ...

Observed

INFO Rewound blockchain to past state  number=14836 hash=366c16..6a00cf
INFO Rewound blockchain to past state  number=14835 hash=3a1f83..ab56bc
INFO Rewound blockchain to past state  number=14834 hash=bdafae..351abb
Fatal: Failed to register the Ethereum service: can't rollback to 14835 which is greater than current 14834

The node rewinds past the requested target (14835 → 14834), then the subsequent SetHead(14835) validation sees 14835 > currentHead(14834) and aborts.

Expected

Rewind to exactly 14835 and start normally at head 14835 (as debug.setHead("0x39f3") does on the go-ethereum-based clients).

Notes / contrast

  • The same node without --set-head loads cleanly at 14835 (Loaded most recent local block number=14835), so the underlying DB is fine — the flag's rewind loop is the problem.
  • debug_setHead is not exposed over RPC on this build (the method debug_setHead does not exist/is not available), so --set-head is the only rollback mechanism available to operators, which makes this bug more painful.

Likely cause

The rewind routine appears to walk down to the first block with available state (14834 here, one below the requested 14835 because 14835's state boundary condition is off-by-one), then re-validates the requested target against the new current head and errors because it went one too far. Suspect an off-by-one / >= vs > in the SetHead target validation or in the "rewind to block with state" loop.

Impact

Medium — blocks a documented recovery workflow (--set-head) and, combined with the missing debug_setHead RPC, leaves operators with no working way to drop an uncommitted tail without wiping+resyncing the datadir.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions