github Galxe/gravity-sdk v1.9.3
Gravity v1.9.3

5 hours ago

Forward epoch sync hardening — not a hardfork

Gravity v1.9.3 backports the forward-epoch-sync client/serving hardening from main (#834#839) onto the v1.9 line. It only changes how a node that is behind the chain fetches and verifies committed blocks from its peers, and how a serving node builds and admits those requests.

This release is not a hardfork and does not change any consensus or execution rule:

  • No hardfork schedule change. Mainnet Beta / Osaka stay at 1787018400 (2026-08-18 02:00:00 UTC) exactly as in v1.9.1 / v1.9.2.
  • No consensus-rule, state-transition, or block-format change. A v1.9.3 node produces and validates byte-identical blocks and state to a v1.9.2 node.
  • No ConsensusDB schema change, no genesis change.
  • No gravity-reth / gravity-aptos pin change.
  • Wire-compatible with v1.9.2 for existing tags. One new error variant is appended (ForwardEpochSyncError::Disabled, BCS tag 7); older tags keep their numbers. Mixed v1.9.2 / v1.9.3 fleets interoperate — a peer that rejects or times out still drives the client toward another peer or legacy reverse sync when appropriate.
  • Opt-in and default off. Runtime behaviour is unchanged unless ENABLE_FORWARD_EPOCH_SYNC=true is set.

Upgrading is optional and can be rolled out node by node — there is no coordinated activation and no deadline. Operators already running forward sync on v1.9.2 should upgrade: v1.9.2 treated the first Busy as a reason to fall back to legacy, which is what this release fixes.

What's changed

Retry on Busy, bound by progress — not by one peer (#834#839)

Follow-up to the batched forward epoch sync shipped in v1.9.2 (#819 / #832#833).

Client

  • Prepare failures are classified instead of all mapping to legacy:
    • Busy — peer stays in the candidate list; the pass finishes the other candidates first; passes that saw Busy back off 0.5 / 1 / 2 s (±25 % jitter), at most 3 times, then the attempt returns Resume so the next epoch-change trigger asks again — never legacy for Busy alone.
    • Explicit rejection (Disabled, EpochNotFound, AnchorMismatch, Internal, …) — peer removed; legacy only once the list is empty.
    • RPC timeout / error — one more probe for that peer (a timed-out probe waits 5 s before the re-ask), then removed.
  • One Prepare attempt is capped at 90 s (nine 10 s probes). At the cap: Busy seen → Resume, otherwise legacy. Mixed fleets therefore reach a decision within 90 s regardless of peer count.
  • Fetch progress watchdog: every 60 s window must deliver ≥ 600 blocks (judged at page boundaries); a failed window returns Resume. Busy during Fetch back offs 0.5 → 8 s (±25 % jitter) on the same serving peer, never past the window.
  • EpochSyncOutcome::Resume keeps everything already persisted and replayed; the next trigger re-Prepares from the advanced ordered_root. Logged at info; does not bump ERROR_COUNT.
  • Prepare timeout is fixed to the network inbound cap (10 s). The v1.9.2 env override FORWARD_EPOCH_SYNC_PREPARE_TIMEOUT_MSEC is removed — waiting longer cannot buy more server work once the network layer has dropped the RPC.

Serving

  • Feature off → new error Disabled; feature on but RoundManager not ready → Busy (both were Internal before).
  • Cold index builds run off the RPC path (spawn_blocking, singleflight join, index LRU of 4 epochs). A Prepare whose build is still running 8 s after the request was received answers Busy in band and keeps building.
  • Quotas (default 4, env-overridable): FORWARD_EPOCH_SYNC_COLD_BUILD_QUOTA (concurrent cold builds) and FORWARD_EPOCH_SYNC_FETCH_QUOTA (concurrent Fetch handlers / admitted peers).
  • One Prepare handler and one Fetch handler per peer, held through BCS encoding and hand-off to the network layer.

RoundManager races the sync against close_rx, so shutdown no longer waits for an in-flight sync.

Operator notes

  • Default behaviour is unchanged when the flag is unset: legacy reverse epoch sync, same as v1.9.1 / v1.9.2 without the flag.
  • Enabling: set ENABLE_FORWARD_EPOCH_SYNC=true in the gravity_node process environment.
    • On the syncing node it selects the forward path (Busy / progress stalls → Resume and retry; legacy only after every candidate rejected or never answered).
    • On a serving node it starts the forward-sync serving task. A serving node without the flag answers Disabled; the client moves on.
    • For the forward path to actually be used, both the catching-up node and at least one upstream peer must have the flag set.
  • Recommended for: VFNs / PFNs catching up many epochs, and the validators / VFNs that serve them — especially any fleet that saw v1.9.2 fall back to legacy on the first Busy.
  • Tuning (new / changed vs v1.9.2):
    • FORWARD_EPOCH_SYNC_COLD_BUILD_QUOTA (default 4) — concurrent cold index builds on a serving node.
    • FORWARD_EPOCH_SYNC_FETCH_QUOTA (default 4) — concurrent Fetch handlers (one per peer, node-wide cap).
    • Invalid / out-of-range values fall back to 4 with a warning.
    • FORWARD_EPOCH_SYNC_PREPARE_TIMEOUT_MSEC is gone — Prepare timeout is hard-wired to 10 s (network inbound cap). Remove it from env files if present; it has no effect.
  • No restart ordering, no coordinated upgrade window. Nodes can be upgraded (and the flag enabled) one at a time.

Docker image

The release pipeline publishes:

docker pull ghcr.io/galxe/gravity_node:v1.9.3

Dependency revision

  • gravity-reth: unchanged at bc817c642c9c3816cc4e22754e13e3c9633419dd
  • gravity-aptos: unchanged at b1f68dc85781ef0d28a568d9d64604b153be9d9e

Full changelogs

Don't miss a new gravity-sdk release

NewReleases is sending notifications on new releases.