Context
During block processing, `State.process_attestations` validates each attestation before applying it. One critical check: the attestation's source checkpoint slot must be justified in the current state.
If the source slot is NOT justified, the attestation is silently skipped — no error is raised, no state change occurs. This is by design: the attestation is simply ignored.
The check in `process_attestations`:
```python
if not self.justified_slots.is_slot_justified(att_data.source.slot, ...):
continue # Skip unjustified source
```
This is important because:
- Validators may reference checkpoints that the proposer hasn't seen justify yet
- Blocks should not be rejected for containing such attestations
- The attestation is simply ineffective, not invalid
No spec test filler specifically tests the unjustified-source skip path.
What to test
Write a state transition filler that:
- Creates a chain where slot X is NOT justified
- Includes an attestation in a block with `source.slot = X`
- Verifies the attestation is silently skipped (no justification change, no error)
- Includes another attestation with a valid (justified) source in the same block
- Verifies that valid attestation IS processed
Key assertions
- Post-state shows no justification from the unjustified-source attestation
- Post-state DOES show effects from the valid attestation
- No error during block processing
- `StateExpectation` validates that only the valid attestation's effects are visible
Where to add the test
Add to: `tests/consensus/devnet/state_transition/test_justification.py`
Code skeleton
def test_attestation_with_unjustified_source_is_silently_skipped(
state_transition_test: StateTransitionTestFiller,
) -> None:
"""Attestation whose source slot is not justified is ignored without error."""
# Setup:
# 1. Build chain to slot 5 (no justification yet beyond genesis)
# 2. Create attestation A with source=slot 3 (not justified) -> should be skipped
# 3. Create attestation B with source=slot 0 (genesis, justified) -> should work
# 4. Include both in a block
# 5. Verify only B's effects appear in post-state
state_transition_test(
pre=generate_pre_state(num_validators=4, genesis_time=Uint64(0)),
blocks=[
BlockSpec(slot=Slot(1)),
BlockSpec(slot=Slot(2)),
BlockSpec(
slot=Slot(3),
attestations=[
# Attestation with unjustified source (slot 2 is not justified)
# This should be silently skipped
AggregatedAttestationSpec(
validator_ids=[ValidatorIndex(0), ValidatorIndex(1), ValidatorIndex(2)],
slot=Slot(3),
target_slot=Slot(2), # Target
target_root_label="...",
# source_slot would need to reference an unjustified slot
# TODO: verify how to set source explicitly in AggregatedAttestationSpec
),
],
),
],
post=StateExpectation(
slot=Slot(3),
# No justification change from the skipped attestation
),
)
How to run
uv run fill --fork=devnet --clean -n auto -k test_attestation_with_unjustified_source
References
- `State.process_attestations`: `src/lean_spec/subspecs/containers/state/state.py`
- `JustifiedSlots.is_slot_justified`: `src/lean_spec/subspecs/containers/state/types.py`
Context
During block processing, `State.process_attestations` validates each attestation before applying it. One critical check: the attestation's source checkpoint slot must be justified in the current state.
If the source slot is NOT justified, the attestation is silently skipped — no error is raised, no state change occurs. This is by design: the attestation is simply ignored.
The check in `process_attestations`:
```python
if not self.justified_slots.is_slot_justified(att_data.source.slot, ...):
continue # Skip unjustified source
```
This is important because:
No spec test filler specifically tests the unjustified-source skip path.
What to test
Write a state transition filler that:
Key assertions
Where to add the test
Add to: `tests/consensus/devnet/state_transition/test_justification.py`
Code skeleton
How to run
References