# Consensus rule undercounting in reimplementations

Reimplemented consensus counters that skip inputs, truncate on parse errors, or cache height-dependent validity

_Area: DeFi accounting and math bugs. Seen in 3 disclosed reports; latest 2026-07-02. Updated 2026-10-29._

## How it works
A second implementation of a consensus rule must produce exactly the same number or decision as the reference client. The assumption that breaks is "our counter/check is equivalent to theirs." It is not, when the reimplementation uses a different code path (pure-Rust vs FFI), skips an input class, or memoizes a result whose validity depends on context the cache key ignores. The node then accepts what the reference rejects, and the two chains diverge.

## Where it shows up
- Two implementations of the same counting rule in one codebase (e.g. FFI for one path, pure-Rust for another) [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-gf9r-m956-97qx.md)
- Counters built with `try_fold`/iterators that early-return on a parse error, yielding a partial count [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-gf9r-m956-97qx.md)
- Block-wide limits computed per-transaction, or a special input (coinbase) excluded from a count the reference includes [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-jv4h-j224-23cc.md)
- A rule enforced only on the mempool path but not during block validation [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-jv4h-j224-23cc.md)
- "Already verified" caches keyed without height, epoch, expiry, or upgrade binding [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-xvj8-ph7x-65gf.md)

## How to review
For each consensus limit or validity check, diff the reimplementation against the reference client line by line. The invariant: for every block/transaction the reference accepts or rejects, the reimplementation must reach the same decision, including edge inputs (disabled opcodes, coinbase scriptSig, expired transactions). Check that counters aggregate over the same scope (block-wide, all inputs), that parse errors do not silently truncate a count, and that any cache key includes every context-dependent input it depends on.

## Variants seen
- P2SH sigop counter stops at disabled opcodes, undercounting vs the reference [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-gf9r-m956-97qx.md)
- Coinbase scriptSig skipped and P2SH redeem sigops never accumulated at block validation, only mempool [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-jv4h-j224-23cc.md)
- Mempool verification cache skips height-dependent checks (expiry, lock time, upgrade binding) [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-xvj8-ph7x-65gf.md)

## Impact and severity
Each case let the node accept blocks or transactions the reference client rejects, risking a chain split — high severity for consensus code. Severity rises when the gap is silent (no error, just a smaller count) and when it applies to block validation rather than mempool admission, since only the former splits the chain [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-gf9r-m956-97qx.md)[E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-jv4h-j224-23cc.md)[E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-xvj8-ph7x-65gf.md).

## Evidence
- E1: https://bugbountylibrary.com/r/ghsa%3AGHSA-gf9r-m956-97qx.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-jv4h-j224-23cc.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-xvj8-ph7x-65gf.md
