Skip to content

release: v2.5.0 — Zanzibar hardfork on TestNet - #339

Open
envestcc wants to merge 10 commits into
masterfrom
rc_2.5.0
Open

release: v2.5.0 — Zanzibar hardfork on TestNet#339
envestcc wants to merge 10 commits into
masterfrom
rc_2.5.0

Conversation

@envestcc

@envestcc envestcc commented Aug 17, 2026

Copy link
Copy Markdown
Member

v2.5.0-rc0 is tagged in iotex-core (dc036061a). Mergeable once the Docker Hub build for that tag is up (~20 min after tagging).

What

Prepares the v2.5.0 release. The headline is the Zanzibar hardfork, scheduled on TestNet at block 46880641 and not scheduled on MainNet.

Network zanzibarHeight Action for operators
TestNet 46880641 — 2026-08-21 ~10:00 CST Must upgrade before the fork height
MainNet absent Do not upgrade yet — docs stay on v2.4.4

Because Zanzibar is TestNet-only, this is a TestNet-only release: README.md, README_CN.md, archive-node.md and all_in_one_mainnet.sh deliberately remain on v2.4.4, and only the TestNet-facing files are bumped.

Zanzibar activates IIP-59 on-chain voter reward distribution, the BLS proof-of-possession requirement at candidate register/update, the in-contract transfer log topic fix, and the GetCommittedState prestate fix.

Changes

  • genesis_testnet.yamlzanzibarHeight: 46880641, plus the two contract addresses IIP-59 reads:
    • autoDepositContractAddress: io1pvlpc02xft2va4f38ae2nvgqglcxtfytez75c4
    • delegateProfileContractAddress: io19l8qpk08rw0jr4vwguyufva9w4t6aq75q2kt90
  • changelog/v2.5.0-release-note.md — new.
  • v2.4.4 → v2.5.0-rc0 in README_testnet.md, README_CN_testnet.md, scripts/all_in_one_testnet.sh and CLAUDE.md. v2.5.0-rc0 is what is tagged in iotex-core and what TestNet delegates will run; v2.5.0 proper is tagged once the fork has been observed.
  • config_mainnet.yaml / config_testnet.yaml — unchanged. v2.5.0 adds no node-config fields; the only config-package change is the Zanzibar height-ordering check, which validates genesis.

On the two contract addresses

Both were deployed on TestNet for this release by replaying the MainNet creation transactions:

address
AutoDepositRegister io1pvlpc02xft2va4f38ae2nvgqglcxtfytez75c4 / 0x0b3e1C3d464AD4CED5313f72A9b10047f065A48B
DelegateProfile io19l8qpk08rw0jr4vwguyufva9w4t6aq75q2kt90 / 0x2fcE00d9E71B9f21D58e4709c4B3A57557ae83D4

Replaying the creation transaction keeps each runtime byte-identical to MainNet's, which is what matters for AutoDepositRegister — IIP-59 reads its storage slots directly, bypassing the bucket() view for performance, so the slot constants in action/protocol/rewarding/autodeposit/slot_reader.go are tied to that exact layout. Unlike installing the runtime bytecode alone, it also runs the constructors, so owner() is set and pause()/unpause() remain usable.

The AutoDeposit address is the one iotex-hub writes compound registrations to. An earlier draft named a different deployment, which would have left the protocol reading one contract while users registered against another — preferences silently ignored, rewards credited to unclaimed balance instead.

Verification

Loaded genesis_testnet.yaml through genesis.New(): it passes validation, zanzibarHeight starts an epoch ((46880641-1) % 360 == 0) and is an era's first epoch (130225 % 24 == 1), leaving the full 23 epochs before the epoch that era settles at, and epochsPerRewardEra (24) / voterBudgetPerBlock (256) inherit their iotex-core defaults as intended.

Open items for the TestNet rollout

Two things must happen before 46880641 or Zanzibar activates with nothing to pay out:

1. Delegates must opt in. IIP-59 distributes only for candidates with VoterRewardOnchainOptIn set. At the fork block migrateHermesRewardOptIn sets it for any candidate whose reward address is in hermesRewardVaultAddresses — but genesis_testnet.yaml does not override that list, so it inherits the two MainNet Hermes vaults (io19604a05..., io12mgttmf..., which are exactly the two vault addresses in the MainNet contract set). Every TestNet delegate sampled via ioctl bc delegate uses its own address as its reward address, so the migration matches nobody. TestNet delegates have to send SetVoterRewardOptIn themselves.

Optionally genesis_testnet.yaml could carry hermesRewardVaultAddresses: [] to make that explicit — behaviour is identical either way, since an empty list and a list that matches nothing both opt in nobody. Left inherited here; say the word and I will make it explicit.

2. Delegates must set commission portions. A delegate without blockRewardPortion / epochRewardPortion in the DelegateProfile contract is snapshotted at 100% commission, so its voters get nothing. Whether the current TestNet delegate set has these configured is not confirmed here.

Neither is a safety issue — rewards keep flowing through the existing off-chain path — but without them IIP-59 is not actually exercised on TestNet.

Requires iotex-core rc_2.5.0.

envestcc and others added 10 commits August 17, 2026 11:56
Zanzibar is the next fork after Yap. It activates IIP-59 on-chain voter
reward distribution, the BLS proof-of-possession requirement at
candidate register/update, the in-contract transfer log topic fix, and
the GetCommittedState prestate fix.

46850041 starts an epoch ((h-1) % 360 == 0, testnet epoch is
numDelegates 24 * numSubEpochs 15) and sits roughly three days ahead of
the chain tip at the time of writing (46750267, ~2.6s/block).

epochsPerRewardEra (24) and voterBudgetPerBlock (256) are left at their
iotex-core defaults; the genesis loader layers this YAML over them.

Requires iotex-core v2.5.0.
- genesis_testnet.yaml: autoDepositContractAddress and
  delegateProfileContractAddress, the two contracts IIP-59 reads. The
  AutoDepositRegister was deployed on TestNet for this release from the
  same runtime bytecode as MainNet's, so the storage layout the
  direct-slot reader depends on matches; DelegateProfile already existed.
- changelog/v2.5.0-release-note.md.
- v2.4.4 -> v2.5.0 across READMEs, archive-node.md and the all_in_one
  scripts.

config_mainnet.yaml / config_testnet.yaml are untouched: v2.5.0 adds no
new node-config fields, only the Zanzibar height-ordering check, which
lives in genesis.

MainNet has no zanzibarHeight, so v2.5.0 is a no-op upgrade there.
Zanzibar is scheduled on TestNet only, so v2.5.0 is a TestNet release and
MainNet operators should not upgrade yet. Reverts the version bump in the
MainNet-facing files -- README.md, README_CN.md, archive-node.md and
all_in_one_mainnet.sh -- which also un-does an incorrect rewrite of
README.md's transaction-log patch section, where the patch and its release
note genuinely belong to v2.4.4.

TestNet docs, the all_in_one_testnet.sh docker tags and the changelog stay
on v2.5.0. CLAUDE.md and the release note now state the split explicitly.
…ibar

IIP-59 only pays out for candidates with VoterRewardOnchainOptIn set. The
fork-block migration sets it automatically for candidates whose reward
address is one of hermesRewardVaultAddresses, but genesis_testnet.yaml
does not override that list, so it inherits the two MainNet Hermes vaults
-- and every TestNet delegate sampled uses its own address as its reward
address. The migration therefore matches nobody on TestNet and delegates
have to send SetVoterRewardOptIn themselves.

Paired with the existing note about DelegateProfile commission portions,
since either one being unset leaves the distribution path with nothing to
pay out.
Fifth change on the fork. Notes that the height moved out of node config
into genesis, so blackListRemovalHeight in config.yaml is no longer read.
Sixth change on the fork. Spells out which ABI entry a BLS-less
registration uses, since the amount travels in a different field there.
…r fixes

The opt-in section claimed the fork-block migration "matches nobody" on
TestNet. That was written from a four-delegate sample and is wrong: three
of the 35 delegates route rewards through a Hermes vault and will be
opted in automatically. Replaced with the full survey -- 3 vault-routed,
2 with commission configured, 1 with both -- which makes the same point
without overstating it, plus the ioctl commands to check any delegate.

Also records what landed in v2.5.0 after this note was first written:
the three fixes carried over from master (#4910 SELFDESTRUCT transaction
log, #4921 sender eviction on a mint panic, #4937 block-level gasUsed),
the abandoned-drain terminal state, and the new ioctl surface.

Notes that the DelegateProfile write must come from the candidate's
identifier address, since sending it from the operator address writes a
record nothing reads.
Height moves from 46850041 to 46880641, an era's first epoch rather than
a point 12 epochs into one. Both are safe -- the constraint that matters
is leaving enough room before the settling epoch, and 46850041 had 12
epochs of it -- but starting an era means the first settlement Zanzibar
takes part in is one it has been active for from the beginning, with the
full 23 epochs of margin.

  height          46880641
  epoch aligned   (h-1) % 360 == 0, epoch 130225
  era start       130225 % 24 == 1
  settles at      epoch 130248, 23 epochs later
  ETA             2026-08-21 ~10:00 CST

Note the era's *first* epoch is epochNum % 24 == 1, not == 0: the latter
is where IsEraBoundary fires, which is the epoch the era settles *at the
end of*. Scheduling activation there is the one case that loses that
era's rewards, because the freeze it needs happens ~1.5 epochs earlier,
before the fork is live.

TestNet docs, the all_in_one_testnet script and the release note's
upgrade steps now name v2.5.0-rc0, which is what is tagged and what
delegates will run; v2.5.0 proper is tagged after the fork is observed.
MainNet docs stay on v2.4.4.
…osit

Both addresses previously named deployments whose owner keys are not
available to this release.

autoDepositContractAddress named a deployment whose `owner()` is the zero
address — its bytecode carries pause()/unpause()/transferOwnership, so
those are permanently uncallable. It also differed from the address
iotex-hub writes compound registrations to, which would have left the
protocol reading one contract while users registered against another:
compound preferences silently ignored, rewards credited to unclaimed
balance instead.

The replacements were deployed by replaying the mainnet creation
transactions, so both runtimes are byte-identical to mainnet's and the
constructors ran normally, leaving the owner set. Verified on chain:
owner is the release key, paused is false, DelegateProfile has the three
reward-portion fields registered against the existing PermyriadVerifier,
and an arbitrary address can publish portions.

The three delegates holding published portions on the old DelegateProfile
were migrated across and their values reconciled value by value.

  delegateProfile  io19l8qpk08rw0jr4vwguyufva9w4t6aq75q2kt90
                   0x2fcE00d9E71B9f21D58e4709c4B3A57557ae83D4
  autoDeposit      io1pvlpc02xft2va4f38ae2nvgqglcxtfytez75c4
                   0x0b3e1C3d464AD4CED5313f72A9b10047f065A48B

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…he fork

Three things were out of sync with genesis_testnet.yaml.

The contract addresses. 02b0476 replaced both deployments; the note still
named the old ones, including in a yaml block operators might copy. It now
carries both the bech32 and 0x forms and explains what changed: replaying
the MainNet creation transactions keeps each runtime byte-identical --
which is what AutoDepositRegister needs, since IIP-59 reads its storage
slots directly -- while letting the constructors run, so the owner is set.
It also records that the AutoDeposit address is the one iotex-hub writes
to, which is the property the previous deployment lacked.

A stale claim. The note told operators to refresh config_testnet.yaml as
well. Only the genesis changed.

The READMEs. README_testnet.md and README_CN_testnet.md -- the docs a
delegate actually follows -- said nothing about the fork. Bumping the image
tag there was not enough: an operator upgrading an existing node has no
reason to re-download the genesis, and nothing told them there is a
deadline. Both now carry a notice above the body with the height, the date,
the requirement to re-download the genesis specifically (it changed twice,
so a stale copy is plausible), and the two ioctl steps for IIP-59.

Drops the "two things to do before the fork" section: the ioctl section
below already covers opt-in and the reward-portion setup, and the survey
numbers in it were a snapshot that would go stale before the fork.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant