Heimdall introduces v0.10.0-beta-candidate, a critical hardfork release centered around the Ithaca upgrade.
The main feature, the Ithaca hardfork, is designed to prevent the chain from halting. Previously, if a block producer stalled while a milestone was pending (with between 1/3 and 2/3 of validator support), the chain would wedge with no way to recover on its own. Ithaca introduces a hardfork-gated rule that allows the network to autonomously recover from such a stall by starting a new span, ensuring the chain continues to operate.
Ithaca Hardfork Activation Heights:
This is a mandatory upgrade for all validators, who must update their nodes before the specified Heimdall block height (not Bor block height) for their respective network:
Amoy: Block 40,776,000
Mainnet: Block 50,185,000
Until these heights are reached, the chain's behavior remains unchanged.
Other Key Improvements:
This release also includes several important updates that take effect immediately upon upgrade:
Mempool DoS Guard: A new safeguard limits transactions to a maximum of 16 messages during `CheckTx`. This prevents a single, oversized transaction from consuming excessive node resources as it's gossiped across the network.
Granular Per-Module Logging: Node operators now have more precise control over logging. The `log_level` can be configured for individual modules (e.g., `"*:info,mempool:debug"`), allowing for targeted debugging without the overwhelming volume of global debug logs.
Transaction Fee Nil-Guard: A hardening measure that prevents nodes from panicking when processing a malformed transaction that lacks fee information. The transaction will now fail validation cleanly instead of crashing the node.
VeBLOP Producer Fallback: In cases where the producer candidate set for a future span becomes empty, a new deterministic fallback mechanism ensures a producer is always selected, preventing the chain from getting stuck.
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-beta-candidate ## 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) - 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 (the ~15-min stress-test halt). 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. 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