Context
When a validator is the proposer for a slot, it calls `Store.produce_block_with_signatures` to build a block. This method gathers pending attestations from the store (both gossip signatures and aggregated proofs) and includes them in the block body.
The block building process (`State.build_block`) filters attestations for validity:
- Source must match the current justified checkpoint
- Target root must exist in historical block hashes
- Target slot must be justifiable after the finalized slot
No spec test filler currently tests block production end-to-end. The existing fillers use `BlockSpec` which auto-fills attestations, but don't test the `produce_block_with_signatures` path where the store selects which attestations to include.
What to test
Write a fork choice filler that:
- Builds a short chain
- Gossips attestations from multiple validators (via `AttestationStep`)
- Ticks to the proposal interval of the next slot
- Produces a block (via `BlockStep`) and verifies the block contains the expected attestations
- Uses `StoreChecks(block_attestation_count=..., block_attestations=[...])` to verify attestation content
Key assertions
- The produced block includes attestations from the gossip phase
- `block_attestation_count` matches the number of distinct `AttestationData` entries
- `block_attestations` checks verify the correct participants and target slots
Where to add the test
Create a new file: `tests/consensus/devnet/fc/test_block_production.py`
Code skeleton
"""Block production tests."""
from consensus_testing import (
AggregatedAttestationCheck,
AttestationStep,
BlockSpec,
BlockStep,
ForkChoiceTestFiller,
GossipAttestationSpec,
StoreChecks,
)
from lean_spec.subspecs.containers.slot import Slot
from lean_spec.subspecs.containers.validator import ValidatorIndex
def test_produce_block_includes_pending_attestations(
fork_choice_test: ForkChoiceTestFiller,
) -> None:
"""Block production includes attestations accumulated via gossip."""
fork_choice_test(
steps=[
BlockStep(block=BlockSpec(slot=Slot(1), label="block_1")),
BlockStep(block=BlockSpec(slot=Slot(2), label="block_2")),
# Gossip attestations from validators 1 and 2
AttestationStep(
attestation=GossipAttestationSpec(
validator_id=ValidatorIndex(1),
slot=Slot(2),
target_slot=Slot(2),
target_root_label="block_2",
),
),
AttestationStep(
attestation=GossipAttestationSpec(
validator_id=ValidatorIndex(2),
slot=Slot(2),
target_slot=Slot(2),
target_root_label="block_2",
),
),
# Next block should include these attestations
BlockStep(
block=BlockSpec(slot=Slot(3), label="block_3"),
checks=StoreChecks(
head_slot=Slot(3),
block_attestation_count=1, # One AttestationData
block_attestations=[
AggregatedAttestationCheck(
participants={1, 2},
target_slot=Slot(2),
),
],
),
),
],
)
How to run
uv run fill --fork=devnet --clean -n auto -k test_produce_block_includes
References
- `Store.produce_block_with_signatures`: `src/lean_spec/subspecs/forkchoice/store.py`
- `State.build_block`: `src/lean_spec/subspecs/containers/state/state.py`
- `AggregatedAttestationCheck`: `packages/testing/src/consensus_testing/test_types/store_checks.py`
- Existing signature aggregation fillers for patterns: `tests/consensus/devnet/fc/test_signature_aggregation.py`
Context
When a validator is the proposer for a slot, it calls `Store.produce_block_with_signatures` to build a block. This method gathers pending attestations from the store (both gossip signatures and aggregated proofs) and includes them in the block body.
The block building process (`State.build_block`) filters attestations for validity:
No spec test filler currently tests block production end-to-end. The existing fillers use `BlockSpec` which auto-fills attestations, but don't test the `produce_block_with_signatures` path where the store selects which attestations to include.
What to test
Write a fork choice filler that:
Key assertions
Where to add the test
Create a new file: `tests/consensus/devnet/fc/test_block_production.py`
Code skeleton
How to run
References