Commit fb5ff3c
refactor: drop model_copy in favour of in-place mutation (leanEthereum#789)
* refactor: drop model_copy in favour of in-place mutation
StrictBaseModel was previously frozen, which forced every state update to
go through model_copy(update={...}) and produced a verbose functional
style throughout the codebase. The frozen constraint was introduced as
groundwork for formal verification, which we are not yet using; in the
meantime the indirection hurts readability.
Changes:
- src/lean_spec/base.py: drop frozen=True from StrictBaseModel and update
the docstring (no longer claims immutability).
- .claude/rules/ssz-patterns.md: drop "immutability" from the SSZModel
principle bullet so the rule matches the new behavior.
- All 115 model_copy(update=...) call sites across src/, tests/, and
packages/testing/ converted to direct field assignment. Intermediate
dict-building variables inlined where the result was used only once.
Exceptions:
- The three JustifiedSlots collection helpers (with_justified,
extend_to_slot, shift_window) still return new instances, but via
type(self)(data=...) instead of model_copy. This preserves the
return-new contract that callers rely on for SSZ collection methods.
- The api_endpoint test fixture's "return self.model_copy(update=handler(
store, self))" pattern becomes a setattr loop over handler's output dict,
then return self.
- MockStore in tests/lean_spec/node/chain/test_service.py loses its
hand-rolled model_copy method (no longer needed; it's a plain dataclass
and is mutable by default).
just check passes (ruff lint + format, ty type check, codespell, mdformat).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: restore hashability and pure-function semantics where required
Dropping frozen globally lost two side effects the codebase relied on:
hashability of value types used as dict keys / set elements, and the
"returns a new state" contract that callers of process_slots and
state_transition were depending on. Both are restored surgically.
Hashability:
- Re-freeze the value types used as dict keys / set elements:
Checkpoint, AttestationData, Attestation, Signature,
TypeOneMultiSignature, TypeTwoMultiSignature, KeyPair,
ValidatorKeyPair, Eth2Data. Per-class model_config override leaves
the stateful types (Store, State, Block, BlockHeader, ENR, etc.)
unfrozen and mutable.
- Add explicit __hash__ on Signature, TypeOneMultiSignature, and
TypeTwoMultiSignature: their nested list-bearing fields break the
auto-derived hash, so the SSZ-encoded bytes drive the hash instead.
Pure-function semantics:
- process_slots now deepcopies its input at entry, matching the
existing docstring ("returns a new state with slot == target_slot").
This makes state_transition and build_block trivially pure.
- Slot assertion relaxed from < to <= so process_slots is idempotent
under repeated calls with the same target (build_block needs this).
- Two Checkpoint mutations in process_block_header replaced with
construction now that Checkpoint is frozen again.
Test fixture in BlockSpec.build_signed_block_with_store deepcopies
the store at entry: the simulation pipeline (on_tick,
on_gossip_attestation, aggregate, accept_new_attestations) mutates
store directly, so the caller's store needs an explicit barrier.
Test fixes:
- _replace_head_at_slot and _add_block_at_slot in test_service.py
construct new Block instances instead of mutating the original
(the AST conversion had broken them).
- Corrupted-proof tests construct new TypeOneMultiSignature rather
than mutating .proof on a frozen instance.
- test_combined_path_rejects_{depth_mismatch,odd_depth} construct
new HashSubTree to avoid polluting the prf_trees fixture across
tests.
- Dropped test_frozen_rejects_assignment on StrictBaseModel (the
base itself is no longer frozen by design).
- Dropped the "store is not store_before" identity check that was
only meaningful when on_gossip_attestation returned a new store.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(spec): restore strict process_slots assertion
Loosening the assertion to state.slot <= target_slot was a workaround
for the mutation cascade through build_block. Now that process_slots
deepcopies its input at entry, the caller's state.slot is never
advanced across repeated calls, so the original strict inequality
holds again. The spec filler test
test_process_slots_target_equal_to_state_slot_rejected was relying on
the strict form.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(forks/lstar): restore Checkpoint frozen override after merge
The merge from main clobbered the per-class `frozen=True` model_config
override on Checkpoint. That override was added in 24a0e7b alongside
the same override on eight other value types used as dict keys / set
members; Checkpoint was the only one that moved files in the merge
(the deleted lean_spec/types/checkpoint.py from leanEthereum#790 collided with the
new home spec/forks/lstar/containers.py from leanEthereum#785) and so it was the
only one whose override was lost.
Without the override AttestationData (which embeds Checkpoint) is no
longer hashable, and on_gossip_attestation crashes when it inserts
into store.attestation_signatures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>1 parent 708b82f commit fb5ff3c
45 files changed
Lines changed: 424 additions & 617 deletions
File tree
- .claude/rules
- packages/testing/src/consensus_testing
- test_fixtures
- test_types
- src/lean_spec
- node
- chain
- networking/enr
- sync
- spec
- crypto/xmss
- forks/lstar
- tests
- consensus/lstar
- fc
- state_transition
- lean_spec
- helpers
- node
- chain
- sync
- validator
- spec
- crypto/xmss
- forks/lstar
- forkchoice
- state
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
32 | | - | |
| 32 | + | |
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| |||
Lines changed: 3 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
283 | 283 | | |
284 | 284 | | |
285 | 285 | | |
286 | | - | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
Lines changed: 4 additions & 12 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
233 | 233 | | |
234 | 234 | | |
235 | 235 | | |
236 | | - | |
237 | | - | |
238 | | - | |
239 | | - | |
240 | | - | |
241 | | - | |
| 236 | + | |
| 237 | + | |
242 | 238 | | |
243 | 239 | | |
244 | 240 | | |
245 | 241 | | |
246 | | - | |
247 | | - | |
248 | | - | |
249 | | - | |
250 | | - | |
251 | | - | |
| 242 | + | |
| 243 | + | |
252 | 244 | | |
253 | 245 | | |
254 | 246 | | |
| |||
Lines changed: 2 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
159 | 159 | | |
160 | 160 | | |
161 | 161 | | |
162 | | - | |
163 | | - | |
| 162 | + | |
| 163 | + | |
164 | 164 | | |
165 | 165 | | |
166 | 166 | | |
| |||
Lines changed: 2 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
| 49 | + | |
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
Lines changed: 2 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
99 | | - | |
| 99 | + | |
| 100 | + | |
100 | 101 | | |
101 | 102 | | |
102 | 103 | | |
| |||
Lines changed: 2 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
63 | | - | |
64 | | - | |
65 | | - | |
| 63 | + | |
| 64 | + | |
Lines changed: 2 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
64 | | - | |
65 | | - | |
| 64 | + | |
| 65 | + | |
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
| |||
Lines changed: 6 additions & 12 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
105 | | - | |
106 | | - | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
107 | 104 | | |
108 | 105 | | |
109 | 106 | | |
| |||
138 | 135 | | |
139 | 136 | | |
140 | 137 | | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
Lines changed: 3 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
283 | 283 | | |
284 | 284 | | |
285 | 285 | | |
286 | | - | |
287 | | - | |
288 | | - | |
289 | | - | |
290 | | - | |
291 | | - | |
292 | | - | |
293 | | - | |
294 | | - | |
295 | | - | |
| 286 | + | |
| 287 | + | |
296 | 288 | | |
297 | 289 | | |
298 | 290 | | |
299 | 291 | | |
300 | 292 | | |
301 | 293 | | |
302 | 294 | | |
303 | | - | |
| 295 | + | |
304 | 296 | | |
305 | 297 | | |
306 | 298 | | |
| |||
0 commit comments