Skip to content

Node update 2.3.0: the ImpactZero security patch

Last updated Written by the ImpactZero validator team

In short

ImpactZero runs Nimiq node 2.3.0 since 07:25 UTC on 3 November 2026, about eight hours after the release. The container was stopped for about 3 seconds and back in consensus about 5 seconds after the stop. No slot was missed, the next block we were scheduled for was produced on time, and staking rewards kept flowing.

Why we updated the morning after

Nimiq published v2.3.0 at 22:58 UTC on 2 November 2026 and asked node operators to update as soon as possible: the release carries important security fixes. Like every 2.x release it is backwards compatible, so the timing was ours to choose. We chose the first quiet moment: about eight hours after the release and roughly six hours before the next epoch boundary.

The validator was elected for epoch 1,446 with slots 207 to 211, so it was actively producing blocks when we started. That is why the moment mattered: a swap that lands on one of your own slots costs that slot's reward.

Before touching anything: the rollback plan

Same rule as the 2.2.0 update: nothing gets stopped until everything needed to undo it exists.

  1. The new image, verified. The 2.3.0 image was pulled in advance and run once on its own to confirm it reports nimiq-client 2.3.0.
  2. The old image, tagged. The running 2.2.0 image was retagged 2.2.0-rollback, so a rollback is a one line container change.
  3. The container spec, exported. The full docker inspect of the working container was saved, so the replacement was created with exactly the same data volume, peer port and restart policy.
  4. The data directory, snapshotted while stopped. New this time: the copy of the node's data (577 MB) was taken in the second between stopping the old node and starting the new one. A copy of a stopped node is guaranteed consistent; it cost about 1.3 seconds.

The node's watchdog and the restake bot were paused for the few seconds of the swap so neither would act on a node that was intentionally down, then restored from a single backup and checked line by line against it.

The swap, second by second

Everything below comes from the node's own logs and the chain, timestamps included.

Time (UTC, 3 Nov)StepEvidence
07:24:592.2.0 node stopped; last network block before the stop is #65,902,408docker stop, block timestamps
07:25:00Data snapshot complete (577 MB)snapshot step log
07:25:022.3.0 container started on the same data volumecontainer StartedAt
07:25:04Consensus establishedRPC isConsensusEstablished answers true
07:25:12Status line: head #65,902,420, 53 peersnode log line "Consensus: established"
07:30:09First block produced on 2.3.0: micro block #65,902,720node log "Our turn", chain records us as producer
07:33:19Restake bot's first full cycle after the swap: 129.14 NIM paid outrestake ledger

Stop to consensus: about 5 seconds, against about 11 for the 2.2.0 update in September. The swap landed between our own blocks: the next block assigned to ImpactZero came five minutes later and was produced on time.

The checks that said it worked

A node that has restarted and a node that is producing are different claims. These checks separated them:

  • Version: the running node reports nimiq-client 2.3.0.
  • Checkpoint: zero checkpoint mismatches. Each release ships a new mainnet checkpoint (election block 64,627,200 for this one); a mismatch would stop the node from progressing.
  • Validator record: active, not jailed, not retired, same balance and staker count as before the swap.
  • Blocks produced: the next block assigned to us, micro block #65,902,720, was produced on time, and the chain lists ImpactZero as its producer.
  • No penalties: zero skip blocks on the network around the swap and an empty punished set in every macro block from 07:23 to 07:30 UTC.
  • Zero panics and zero error lines in the node log since the start, with 83 peers connected twenty minutes later.

What 2.3.0 actually changes

The release notes, translated into what each change means for a validator:

  • A transaction cache that could skip a signature check. A shortcut in transaction verification could accept a cached transaction without checking its signature in one edge case. Fixed: every transaction is checked properly. This is the headline security fix.
  • Less work for unauthenticated data. Election voting keys are now decompressed only after a block is authenticated, block bodies passed between peers have a size limit, and double vote proof signatures are decompressed only when needed. A misbehaving peer can no longer make a node do expensive work with data it has not proven.
  • Tighter connection limits. Connection pool accounting was fixed and per subnet limits were tightened, which makes it harder to crowd out a node's peers from a single network range.
  • Stricter proof checks. Proofs with duplicate positions and malformed trie keys are now rejected.
  • More reliable state sync. Partial state stays consistent during sync and recent state changes are kept until the next macro block, so a syncing node is less likely to stumble.
  • Validator blocks are published only after they are accepted. A validator no longer announces a block it failed to apply itself.
  • A consensus edge case. Macro block voting can now finish on a late proposal that completes a set of votes it already holds, instead of losing a round.
  • Stability and dependencies. A memory leak in address subscriptions and a race condition were fixed, cryptography libraries got constant time fixes, and dependencies were updated for published security advisories.
  • Refreshed mainnet checkpoint at election block 64,627,200, so new and resyncing nodes start faster.

The ten minutes the restake bot waited

One thing happened around the update that was not the update. Before paying anyone, our restake bot checks the validator's recent incoming transactions so that a wrong send is never paid out as if it were a reward. That check reads from a public history service, and from just before the swap until 07:31 UTC that service was not answering. The bot did what it is built to do: it held its cycles rather than pay on unchecked data.

Held cycles lose nothing. Rewards stay in the validator's reward wallet, and the first good cycle at 07:33:19 UTC paid out the 129.14 NIM that had built up, split across every staker by the usual formula. Every cycle since then has run on schedule.

For other validator operators

The short version: pull and test the new image first, tag the running image for rollback, export the container spec, pause anything that would react to the node going down, then stop, snapshot, remove and recreate with the same spec. Watch for consensus and for your first produced block before you call it done, and check the punished set in the macro blocks after your swap. Pick the moment by your own slots and the epoch boundary rather than the clock: done that way, the update is a five minute job with a few seconds offline.

Questions people ask

How long was the validator offline?
The old container stopped at 07:24:59 UTC, the 2.3.0 container started at 07:25:02 and the node reported consensus at 07:25:04. That is about 5 seconds from stop to consensus. None of our slots fell inside that window, so no block was missed: the network produced no skip blocks around the swap and every macro block in that stretch carried an empty punished set.
What does 2.3.0 change for stakers?
Nothing in the fee, the payout basis or the restake schedule: those are our policy, not part of the node release. What 2.3.0 changes is the node software itself, mostly security hardening: a fix for a transaction cache that could skip a signature check, tighter limits on what a peer can make a node do before it has proven anything, stricter proof checks, more reliable state sync, and a refreshed mainnet checkpoint.
Why update so quickly?
The release is backwards compatible with the 2.x line, so nothing forces a swap on a schedule. Nimiq asked node operators to update as soon as possible because of the security fixes, and a validator is exactly the kind of node those fixes protect. We updated about eight hours after the release, at a moment far from an epoch boundary.
What happens if the update goes wrong?
Rollback was staged before the stop: the 2.2.0 image is kept under a rollback tag, the data directory was copied while the node was stopped, and the full container spec was exported. Pointing the container back at the rollback image resumes the old node from its own data, in seconds.
Did stakers lose any rewards?
No. The protocol did not penalise the validator, and the restake bot kept nothing back. For about ten minutes around the swap the bot held back its cycles because the public history service it uses for its safety check was not answering. Held cycles are not lost: the first good cycle at 07:33:19 UTC paid out the 129.14 NIM that had built up, split across all stakers as usual.

Sources