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-aptospin change. - Wire-compatible with v1.9.1. The new
ConsensusMsgvariants 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=trueis 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
Busyunder 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_infocolumn 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_MSECenv 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=truein thegravity_nodeprocess 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(default30000, must be ≥ 1) — client-side per-attempt Prepare timeout. Raise it if serving peers with very tall chains log slowBuilt forward epoch sync indextimes. - 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
Busyimmediately 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 logBusyunder 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.2Dependency revision
gravity-reth: unchanged atbc817c642c9c3816cc4e22754e13e3c9633419ddgravity-aptos: unchanged atb1f68dc85781ef0d28a568d9d64604b153be9d9e
Full changelogs
- gravity-sdk v1.9.1...v1.9.2
- Backport PR: #833 (cherry-picks of #819 and #832 from
main)