Skip to content

refactor(testing): base tamper specs on StrictBaseModel - #909

Merged
tcoratger merged 1 commit into
leanEthereum:mainfrom
tcoratger:refactor/tamper-specs-strict-base-model
Jun 10, 2026
Merged

refactor(testing): base tamper specs on StrictBaseModel#909
tcoratger merged 1 commit into
leanEthereum:mainfrom
tcoratger:refactor/tamper-specs-strict-base-model

Conversation

@tcoratger

Copy link
Copy Markdown
Collaborator

Motivation

The proof and signature tamper specs in verify_proofs.py (5 classes) and verify_signatures.py (6 classes) inherited raw pydantic.BaseModel — the only models in the package to do so. Every other spec and fixture model inherits StrictBaseModel / CamelModel, which adds:

  • frozen=True — no mutation after construction
  • strict=True — no implicit type coercion
  • extra="forbid" — unknown fields rejected
  • camelCase aliasing with populate_by_name=True

A latent inconsistency in a reference codebase.

What this does

Bases all eleven tamper specs on StrictBaseModel. These are immutable value tags applied during generation and consumed by match dispatch — never mutated — so freezing is safe, and all construction sites already pass correctly typed values (ValidatorIndex(...) for typed fields, plain int for int fields), so strict validation accepts them unchanged.

Why it's behavior-preserving

The SignedBlockTamper / SingleMessageTamper / MultiMessageTamper unions include several structurally identical empty models, so the concern was whether validation might coerce one empty tamper into a sibling and break the match dispatch. Verified it does not:

SetProposerIndex           -> SetProposerIndex           preserved=True
ClearFirstAttestationBits  -> ClearFirstAttestationBits  preserved=True
CorruptProof               -> CorruptProof               preserved=True
AppendPhantomAttestation   -> AppendPhantomAttestation   preserved=True
MutateStateRoot            -> MutateStateRoot            preserved=True
SwapFirstTwoAttestations   -> SwapFirstTwoAttestations   preserved=True

Pydantic v2 keeps an exact union-member instance as-is (revalidate_instances defaults to never), so dispatch is unchanged.

Testing

  • just check passes (lint, format, ty, codespell, mdformat).
  • Fill smoke on verify_proofs + verify_signatures passes; tamper paths emit their distinct rejection reasons (INVALID_BLOCK_PROOF, INVALID_SIGNATURE).
  • Confirmed each tamper preserves its exact type through union validation, and frozen rejects post-construction mutation.

🤖 Generated with Claude Code

The proof and signature tamper specs inherited raw pydantic.BaseModel,
the only models in the package to do so.
They lacked the frozen, strict, and extra-forbid constraints every other
spec and fixture model carries through StrictBaseModel.

Base them on StrictBaseModel for consistency.
The specs are immutable value tags applied during generation and are never
mutated, so freezing them is safe.
All construction sites already pass correctly typed values, so strict
validation accepts them unchanged.

The empty-model union members still preserve their exact type through
validation, so the match dispatch over each tamper is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tcoratger
tcoratger merged commit 336845e into leanEthereum:main Jun 10, 2026
13 checks passed
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