Files
coopops de24cd822e feat(chain): chain-historical-exceptions registry for action_mroot bypass
Adds a generic per-chain exception mechanism that lets a controller skip
the producer_block_id != ab._id assert in apply_block when the only
header divergence is a known historical action_mroot=0 inside a declared
block-number window for the matching chain_id.

Motivation: on 2026-05-11 the mainnet Коопеномикс BP briefly ran a dev
build (v5.2.0-dev-294edf3b8) that did not register the on_activation
handler for ASSERT_RECOVER_KEY_ACCOUNT (id=24). For ~20 minutes (blocks
113273322..113275716) onblock did not register intrinsics,
_action_receipt_digests was empty and finalized action_mroot was the
zero digest. The window is now permanently irreversible. Any current
binary refuses to replay past block 113273322 with
block_validate_exception, so new full-history archive nodes / fresh BPs
/ --hard-replay-blockchain on mainnet are blocked.

Design (see exception-notes.md):
- New struct chain_historical_exceptions { chain_id, windows[] }
  loaded from a JSON file path supplied via the new config option
  chain-historical-exceptions. Absent / empty file => no change in
  behavior, strict upstream Antelope validation. Forks and subnets
  reusing this codebase without supplying a file are unaffected.
- Loader fires from controller_impl::init() right after
  protocol_features.init(db) (where self.get_chain_id() is valid) and
  EOS_ASSERTs that the file's chain_id matches the running chain — so
  the mainnet exception file cannot accidentally be applied to a
  different chain.
- Bypass site is the existing producer_block_id != ab._id branch in
  apply_block. Bypass fires only when all three hold: block_num is
  inside a declared window, b->action_mroot is strictly the zero
  digest, and a new other_header_fields_match helper confirms every
  other header field (timestamp, producer, confirmed, previous,
  transaction_mroot, schedule_version, new_producers,
  header_extensions) matches the locally-assembled block. Any other
  header divergence still throws block_validate_exception as before.
- Every bypass application is logged via wlog with the configured
  reason for forensics.

The data file for mainnet Коопеномикс is shipped separately (lives in
the playbooks repo, deployed to /etc/coopos/exceptions/) so the data
never leaks into forks that simply pull this codebase.

Unit tests cover JSON round-trip, chain_id mismatch -> startup refusal,
in-window action_mroot=0 -> bypass accepts, out-of-window -> rejected,
and in-window-but-other-field-altered -> rejected.

Refs: incident 2026-05-11, coopos commit 2c23b8108 (root-cause fix that
arrived too late for the dirty window).
2026-06-03 06:19:06 +00:00
..
2024-03-11 14:56:54 -04:00
2024-01-03 22:38:48 -05:00
2019-03-14 06:03:04 -04:00