Heimdall v0.10.0 is a mandatory hardfork release that introduces the Ithaca upgrade. All validators must upgrade their nodes before the activation height on their respective networks.
The Ithaca hardfork is scheduled to activate at the following Heimdall block heights:
Amoy: block 40,776,000 (approx. 2026-07-20 ~14:00 UTC)
Mainnet: block 50,185,000 (approx. 2026-07-29 ~14:00 UTC)
Ithaca Hardfork Highlights
The primary goal of Ithaca is to enable autonomous recovery if a Bor block producer stalls while a milestone has pending agreement (between 1/3 and 2/3 of validator votes). Previously, this scenario could halt block production on Bor indefinitely. With Ithaca, if the Bor chain head fails to advance for a set period, Heimdall will automatically rotate to a new span producer, allowing the chain to resume.
The hardfork also includes several related stability improvements:
- Producer Selection Fallback: Prevents a potential chain halt by providing a deterministic fallback to select a new producer if the candidate set becomes empty during future-span creation.
- Producer Validation: Ensures that any selected span producer is an active, positive-power member of the current validator set. This prevents the selection of a stale or zero-power validator, which could otherwise freeze a span.
Other Notable Improvements
This release also brings immediate enhancements for node operators focused on security and usability:
- Mempool DoS Protection: A new limit of 16 messages per transaction is now enforced at the mempool admission stage (`CheckTx`). This hardening protects nodes from potential DoS attacks using oversized transactions without affecting consensus or valid blocks.
- Granular Logging Control: The log_level configuration now supports per-module settings (e.g., "*:info,mempool:debug"). This allows operators to enable detailed logging for a specific subsystem (like mempool or x/milestone) without being overwhelmed by global debug-level output.
- Node Stability Fix: An update to a core dependency prevents a potential node panic that could occur when decoding a malformed, fee-less transaction. This makes nodes more resilient against invalid data from the network.
Release candidate on top of `v0.9.0`, introducing the Ithaca hardfork. Full diff: https://github.com/0xPolygon/heimdall-v2/compare/v0.9.0...v0.10.0 ## Highlights This is a hardfork release. The headline work is the new Ithaca hardfork (autonomous recovery from a producer stall under a pending milestone), gated at a per-network Heimdall block height; until the height is reached, runtime behavior is unchanged. The rest is a mempool DoS guard, a transaction-fee nil-guard, operator-facing per-module logging, and dependency/CI housekeeping. Ithaca activation heights - Amoy at block `40,776,000` (2026-07-20 ~14:00 UTC) - completed! - Mainnet at block `50,185,000` (2026-07-29 ~14:00 UTC) Heimdall block heights (not Bor). All validators must upgrade before their network's height. ### Ithaca hardfork — span rotation on bor pending-head stall Post-Rio, a pending milestone (1/3 <= PM < 2/3) unconditionally suppressed span rotation. If the block producer stalled while a milestone sat in that band, the chain wedged with no autonomous recovery. Ithaca adds a hardfork-gated rule: even under a pending milestoneen the >1/3-agreed bor head stops advancing for a stall threshold, starting the new span atN+1 (no reorg of pending blocks). The >1/3 agreement is the adversarial guard against a fabricated head. **Key pieces:** - `helper` — Ithaca fork gate (per-network activation heights) + configurable bor-stall thresher. - `x/milestone` — carries the validator's actual latest bor head in the milestone vote extension (`latest_block_number` / `latest_block_hash`, emitted post-fork only); tallies a >1/3 agreed head selected by greatest summed voting power (`GetMajorityActualHead`), so a byzantine minority reporting a fabricated the honest tip. Deterministic canonical-parent selection in `GetMajorityMilestoneProposition`. - `x/bor` — honors an explicit excluded-producer set in both active and fallback selection; applies exclusions before the empty check so an exclusion that empties the active set re-triggers the fallback instead of wedging. - `app` — gated `PreBlocker` dispatch that rotates from the agreed actual head +1, bounds the agreed head to the span that owns the stall region, debounces both rotation clocks, and guards span-exhaustion / overflow (`MaxUint64`) boundaries. Extensive adversarial hardening and regression coverage across the milestone tally, VE valid and boundary cases. The new vote-extension fields and pending-headtracking keys are only written/emitted post-fork, so there's no migration of existing state. **VeBLOP producer fallback when candidate set empties (#618)** `SelectNextSpanProducer` could hand an empty candidate slice to `SelectProducer` when the elected producer set collapsed to the current producer and it was filtered out of the active set; in the future-span path that error propagated out of `PreBlocker`. Adds a deterministic, Ithaca-gated fallback that draws ave/supporting set, then the validator set (sorted, excluding current and excluded producers), so future-span creation always has a candidate. Scoped to the future-span path only (`checkAndAddFutureSpan`) via an explicit opt-in flag; non-fatal rotation paths keep their skip-and-retry behavior. **CheckTx mempool DoS guard — `maxMsgsPerTx` (#621)** Caps a single tx at 16 messages in `CheckTx`. An oversized multi-message tx would otherwise trigger the per-message VeBLOP validation loop and full ante processing on every gossip hop. Enforced in `CheckTx` only — block validity and consensus are unaffected. **Transaction fee nil-guard (`cosmos-sdk` fork v0.2.12-polygon, #620)** The tx wrapper's fee accessors (`GetGas`, `GetFee`, `FeePayer`, `FeeGranter`) now return zero/nil when a decoded tx carries no `AuthInfo.Fee` instead of dereferencing it. A fee-less tx can be decoded during mempool admission and gossiped between nodes; without the guard the first fee-reading ante decorator dereferences the nil fee and panics on the processing node. The guard makes such a tx fail validation cleanly. Node-local hardening — no consensus change, no fee-behavior change for well-formed txs. Ships via the `cosmos-sdk` fork bump v0.2.11-polygon to v0.2.12-polygon. **Operator-facing: granular per-module logging (#619)** `log_level` now accepts a per-module form in addition to a single global level: - Single level applied to every module, e.g. `"info"` or `"debug"`. - Comma-separated `module:level` pairs with an optional `*:level` default, e.g. `"*:info,mempool:debug"` — scope debug to one subsystem without the volume of a global debug level.. Levels: trace, debug, info, warn, error, fatal, panic, disabled. The module key is the module field of a log line (CometBFT: consensus, p2p, mempool, blocksync, statesync, …; Heimdall: server, x/bor, x/checkpoint, x/milestone, bridge/processor, …). Documented in the packaged mainnet/amoy config.toml. **Additional fixes** - bor: validate veblop span producer against current validator set by @pratikspatil024 in https://github.com/0xPolygon/heimdall-v2/pull/625 - milestone: [gate actual head fields at Ithaca](https://github.com/0xPolygon/heimdall-v2/commit/93bf5a2b7b720bd50178e44680d529ec77646743), [address comments and bump go version](https://github.com/0xPolygon/heimdall-v2/commit/4feaaad78976030b74cba7c42fdfa34543293e0a) Dependencies & CI - `go.mod` — cosmos-sdk fork v0.2.11-polygon → v0.2.12-polygon (the fee nil-guard above) (#620). - chore — remove SonarQube/SonarCloud integration and sonar-project.properties (#622). - CI — pin kurtosis-pos to v1.3.4 for e2e (#613). Rollout notes - Consensus-affecting hardfork. Ithaca activates at the per-network Heimdall heights above, wired in helper/config.go (Amoy 40,776,000 ≈ 2026-07-20 14:00 UTC; mainnet 50,185,000 ≈ 2026-07-29 14:00 UTC). All validators must upgrade before their network's height; the fork is inert until then. Heightst cadence and may be nudged before the tag if cadence drifts (mainnet is furthest out). - No store migration / ConsensusVersion bump in this release (Ithaca adds only post-fork vote-extension fields and tracking keys, no migration of existing state). - maxMsgsPerTx, the fee nil-guard, and per-module logging take effect immediately on upgrade (require no coordination). Merged PRs - #611 — app, bor, milestone, helper: rotate span when bor pending head stalls - #618 — bor: fall back to eligible producers when veblop candidate set is empty - #621 — app: add maxMsgsPerTx - #619 — cmd, helper, x/bor: granular per-module logs - #620 — go.mod: bump cosmos dep - #622 — chore: remove SonarQube/SonarCloud integration - #613 — fix: pin kurtosis-pos to v1.3.4 - #625 - bor: validate veblop span producer against current validator set ## What's Changed * fix: pin kurtosis-pos to v1.3.4 by @kamuikatsurgi in https://github.com/0xPolygon/heimdall-v2/pull/613 * backport v0.9.0 by @marcello33 in https://github.com/0xPolygon/heimdall-v2/pull/615 * app, bor, milestone, helper: rotate span when bor pending head stalls (POS-3629) by @pratikspatil024 in https://github.com/0xPolygon/heimdall-v2/pull/611 * cmd, helper, packaging, x/bor: granular logs by @marcello33 in https://github.com/0xPolygon/heimdall-v2/pull/619 * go.mod: bump cosmos dep by @marcello33 in https://github.com/0xPolygon/heimdall-v2/pull/620 * app: cap messages per tx in CheckTx by @marcello33 in https://github.com/0xPolygon/heimdall-v2/pull/621 * chore: remove SonarQube/SonarCloud integration by @0xCVH in https://github.com/0xPolygon/heimdall-v2/pull/622 * bor: fall back to eligible producers when veblop candidate set is empty by @pratikspatil024 in https://github.com/0xPolygon/heimdall-v2/pull/618 * bor: validate veblop span producer against current validator set by @pratikspatil024 in https://github.com/0xPolygon/heimdall-v2/pull/625 * v0.10.0 beta candidate by @marcello33 in https://github.com/0xPolygon/heimdall-v2/pull/623 **Full Changelog**: https://github.com/0xPolygon/heimdall-v2/compare/v0.9.0...v0.10.0