Skip to content

Default-config validators: no fast recovery from missed commit message, plus silent late-signature exclusion — both losing real signing % #5092

Description

@hedgeme

Summary

Running a mainnet shard-1 validator (v2026.1.0-gdb22d51, "Bloom") on a config
verified byte-identical to harmony config dump -n mainnet from this exact
binary (only difference: inert ShardData.ShardCount). Over the past ~24
hours we've captured hard evidence of two distinct mechanisms causing real
signing-percentage loss, both reproducible with log traces, neither explained
by local config, hardware, or network issues (all independently ruled out —
detail below). Filing since together they may point at a real gap: default
mainnet config (Sync.Client = false, i.e. not using the experimental
streamsync client) has no fast reactive-recovery path when a single consensus
message is missed
, and a substantial fraction of signing loss appears to
come from the FBFT tight-deadline design working exactly as intended but with
no operator-facing signal.

Mechanism 1: missed commit-confirmation message → no fast recovery on default config

Traced second-by-second from zerolog-harmony.log, 2026-07-31 23:00:00–23:06:51 UTC:

  1. 23:00:04.438 — validator sends its own FBFT Commit vote for block
    94505738, on schedule.
  2. The network's aggregated Committed confirmation for that specific
    block
    never arrives at this node.
  3. 23:00:06+ — Announce/Prepared messages for the next blocks (739, 740,
    741, ...) arrive while still parked waiting on 738's commit confirmation.
    Each rejected: [VerifyNewBlock] Cannot Verify New Block!!!, blockHeight 94505739, myHeight 94505737 / error: unknown ancestor.
  4. consensus.OnCommitted correctly detects this
    ([OnCommitted] low consensus block number. Spin up state sync) and calls
    consensus.spinUpStateSync() immediately (not on a timer).
  5. However, spinUpStateSync() only does consensus.dHelper.DownloadAsync(),
    and consensus.dHelper is only initialized via SetDownloader(), which
    cmd/harmony/main.go gates behind if hc.Sync.Client && .... Since
    Sync.Client = false is the binary's own default (confirmed via fresh
    harmony config dump -n mainnet diff), this fast path appears to be a
    no-op on any node running default config.
  6. Actual recovery instead comes from a separate, independent, much slower
    periodic legacysync check (getMaxPeerHeight / EPOCHSYNC cycle,
    observed running on a ~20-40s cadence). Full recovery took until
    23:06:51~6.5 minutes, during which the node correctly detects
    OUT OF SYNC repeatedly but cannot sign.
  7. Network-wide block cadence stayed completely normal throughout (204
    blocks in 407s, ~2.0s/block, cross-checked against public RPC) — only
    this node fell behind. Confirmed identical signature (unknown ancestor
    cascade + ~5-7 min self-heal) present in this node's logs going back to
    at least 2026-07-28, so not a new regression, and reproduced 7 times in
    19 hours of monitoring before this report.

Is this a known limitation of running with default (non-streamsync) config?
Is there a non-experimental way to shorten this recovery window, or is a
faster reactive path planned for the legacysync/default path specifically
(not just the experimental streamsync client)?

Mechanism 2: silent late-signature exclusion, zero local trace

Observed live during this report, 2026-08-01 02:00:02–02:05:02 UTC:

  • Two consecutive 5-minute sampling ticks: 28/127 missed (22.0%), then
    30/129 missed (23.3%).
  • No unknown ancestor errors, no stall — local block height advanced
    normally throughout, gap vs. public reference stayed at 0 (briefly -9,
    i.e. momentarily ahead of a lagging public RPC, not behind).
  • Only routine timeout reason was overwritten phase-timer log lines
    present — nothing that would let an operator distinguish this from
    healthy operation without external signing-percent monitoring.

This matches Harmony's own "1-Second Finality" design notes: FBFT proceeds
once 2/3 of signatures arrive per phase and explicitly excludes late
signatures past a tight per-block deadline, with no error generated on the
excluded validator's side. Understood this is by design, not a bug — but
raising because it means a well-connected, fully-synced, default-config
validator can lose 20%+ of an epoch's signing with zero actionable local
signal
, which seems worth documenting/exposing (e.g. a debug-level metric
or log line specifically for "signature arrived but too late to include")
so operators aren't left inferring this purely from external explorers.

Environment

  • v2026.1.0-gdb22d51 (built 2026-07-08, current mainnet release)
  • Config: harmony config dump -n mainnet from this binary, zero
    modifications besides inert ShardData.ShardCount
  • Wired gigabit, no Wi-Fi hop, TCP 9000 forwarded and externally verified
  • p2p-connectivity.connected: 368-621 throughout every incident (healthy)
  • NIC hardware/driver error counters (rx_errors, rx_crc_errors,
    rx_missed_errors, etc.): all 0
  • No NVMe/ATA/MCE/EDAC kernel warnings in dmesg for the full incident window
  • chrony synced, ~32µs offset from NTP at time of worst incident

What we're not asking

Not asking for the streamsync client itself to be un-experimental — we
understand that's a deliberate, ongoing effort. Asking specifically about
the default (legacysync) path's recovery behavior and whether Mechanism 2's
silence is expected/by-design or something worth better operator-facing
visibility for.

Happy to provide full raw logs for either window if useful.

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