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

4 hours ago

Network-layer performance optimization — not a hardfork

Gravity v1.9.2 backports the batched forward epoch sync from main onto the v1.9 line. It only changes how a node that is behind the chain fetches and verifies committed blocks from its peers (the consensus networking / block-sync layer).

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.
  • No consensus-rule, state-transition, or block-format change. A v1.9.2 node produces and validates byte-identical blocks and state to a v1.9.1 node.
  • No ConsensusDB schema change, no genesis change.
  • No gravity-reth / gravity-aptos pin change.
  • Wire-compatible with v1.9.1. The new ConsensusMsg variants are appended, so existing BCS tags are unchanged. Mixed v1.9.1 / v1.9.2 fleets interoperate; a peer that does not support or rejects the forward sync request simply causes the client to fall back to the legacy reverse sync path.
  • Opt-in and default off. Nothing changes at runtime unless ENABLE_FORWARD_EPOCH_SYNC=true is set (see operator notes).

Upgrading is therefore optional and can be rolled out node by node — there is no coordinated activation and no deadline.

What's changed

Batched forward epoch sync (#819#833)

A node catching up across epochs (e.g. a VFN / PFN that was offline, or a fresh full node) previously walked committed blocks backwards from the epoch-ending ledger info, one round-trip at a time. v1.9.2 adds a versioned, block-number-anchored forward epoch sync RPC:

  • The syncing node fetches, persists, and replays one bounded batch of blocks before requesting the next, walking forward through the epoch.
  • Every batch is authenticated end to end: signed blocks, QCs, ledger infos, parent links, and a pinned manifest-serving peer. Block numbers are only cursors — hashes and consensus proofs remain authoritative.
  • The serving side is bounded by a four-request semaphore and answers Busy under load instead of queuing unbounded work.
  • The legacy reverse-sync path is kept as the rolling-upgrade fallback.

Observed in testing: a VFN that was hundreds of blocks behind caught up to the exact live height, and a late-joining empty validator caught up from block 330 to 841 and then proposed and voted normally.

Bound the serving-side index build to the requested epoch (#832#833)

Follow-up fix for the serving side of #819:

  • Fix (memory): building the forward-sync index scanned the entire ledger_info column family (one row per commit since genesis, never pruned) and filtered by epoch in memory. On a node at height ~33M this materialized ~15 GiB and OOM-killed a 14 GiB VFN minutes after the first inbound request, repeating on every restart. It now reads only [first_block_number, target_block_number] via RocksDB iterate bounds — peak memory drops from whole chain to one epoch. RSS stayed ~1 GiB on the same node that was previously killed at 14.2 GiB.
  • Fix (CPU): ledger infos are matched to their certifying position with a one-pass commit_id → (round, position) map instead of rescanning every QC per ledger info (O(L·Q)O(L + Q)).
  • Serving-side info logs: Received forward epoch sync request, Built forward epoch sync index (entries / boundaries / build time), Responded forward epoch sync request, plus a warning when the per-peer queue drops a request.
  • FORWARD_EPOCH_SYNC_PREPARE_TIMEOUT_MSEC env for the client's per-attempt Prepare timeout, default raised to 30000 so a mature serving peer's first (cold) index build is not raced into the legacy path.

Testnet acceptance: a 14 GiB LA VFN at height 33.25M served a PFN ~1150 epochs behind; cold index builds took 4.5–5.7 s per ~29k-block epoch, 3.2k fetch pages answered at ~1 ms, no Busy / queue-full / errors, and the PFN crossed epoch boundaries on the forward path without falling back.

Operator notes

  • Default behaviour is unchanged. Without any configuration, v1.9.2 nodes use the same legacy reverse epoch sync as v1.9.1.
  • Enabling: set ENABLE_FORWARD_EPOCH_SYNC=true in the gravity_node process environment.
    • On the syncing node it selects the forward path (falls back to legacy on any error).
    • On a serving node it starts the forward-sync serving task. A serving node without the flag immediately rejects forward requests, and the client falls back to legacy — so enabling only one side is safe, it just has no effect.
    • For the forward path to actually be used, both the catching-up node and at least one of its upstream peers must have the flag set.
  • Recommended for: VFNs / PFNs that need to catch up many epochs, and the validators / VFNs that serve them.
  • Tuning: FORWARD_EPOCH_SYNC_PREPARE_TIMEOUT_MSEC (default 30000, must be ≥ 1) — client-side per-attempt Prepare timeout. Raise it if serving peers with very tall chains log slow Built forward epoch sync index times.
  • Serving-side load bounds: a serving node handles at most 4 forward-sync requests concurrently (Prepare and Fetch share the limit) and queues at most 1 pending request per peer; anything beyond that is answered Busy immediately and the client falls back to legacy sync. Cold index builds for different epochs are serialized, so a serving node that has to build several epochs back-to-back may log Busy under a burst of catch-up peers — this is expected and self-limiting.
  • 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.2

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.