Skip to content

test: add consensus-critical justification test vectors#509

Merged
tcoratger merged 2 commits intoleanEthereum:mainfrom
Pier-Two:test/add-justification-test-vectors
Apr 7, 2026
Merged

test: add consensus-critical justification test vectors#509
tcoratger merged 2 commits intoleanEthereum:mainfrom
Pier-Two:test/add-justification-test-vectors

Conversation

@uink45
Copy link
Copy Markdown
Contributor

@uink45 uink45 commented Apr 6, 2026

🗒️ Description

Add 9 new consensus-critical justification tests to test_justification.py. All tests use the StateTransitionTestFiller pattern, generating JSON fixtures for client implementations.

New tests:

Test Rule Covered
test_repeated_validator_does_not_double_count_within_same_block Same validator in two attestations within one block counts once
test_pending_justification_survives_finalization_rebase Pending votes for valid targets survive when finalization rebases the window
test_split_supermajority_aggregations_in_same_block_justify Multiple aggregates for the same target merge within a single block
test_odd_validator_threshold_boundary_justifies 4/5 validators (80%) meets the 2/3 integer threshold
test_odd_validator_threshold_boundary_does_not_justify 3/5 validators (60%) does not meet the 2/3 integer threshold
test_supermajority_with_mismatched_target_root_is_ignored Supermajority with wrong canonical root is rejected
test_justification_clears_only_the_resolved_target_votes Justifying one target clears only that target's pending votes
test_finalization_prunes_stale_pending_votes_and_rebases_window Finalization prunes stale pending votes and shifts the tracking window
test_target_at_or_before_source_is_ignored Votes targeting a slot at or before the source are rejected

Additional changes:

  • Enhanced test_votes_accumulate_across_blocks with pending vote cleanup assertions (justifications_roots, justifications_validators)
  • Renamed test_exact_two_thirds_threshold_justifies to test_even_validator_threshold_boundary to pair with the new odd-validator counterpart

🔗 Related Issues or PRs

N/A

✅ Checklist

  • Ran tox checks to avoid unnecessary CI fails:
    uvx tox
  • Considered adding appropriate tests for the changes.
  • Considered updating the online docs in the ./docs/ directory.

Add 9 new justification tests covering previously untested consensus
rules. All tests use the StateTransitionTestFiller pattern to generate
JSON fixtures for client implementations.

New tests:
- Split aggregate merging within a single block
- Same-block duplicate validator dedup
- Odd validator set threshold boundary (4/5 pass, 3/5 fail)
- Mismatched target root rejection
- Isolated vote cleanup (only resolved target cleared)
- Pending vote survival across finalization rebase
- Stale vote pruning on finalization advance
- Backward vote rejection (target <= source)

Also enhances test_votes_accumulate_across_blocks with pending vote
cleanup assertions, and renames test_exact_two_thirds_threshold_justifies
to test_even_validator_threshold_boundary.
@uink45
Copy link
Copy Markdown
Contributor Author

uink45 commented Apr 6, 2026

Hi @tcoratger and @unnawut, let me know if you have any feedback on this, and I can make adjustments to the changes.

Copy link
Copy Markdown
Collaborator

@tcoratger tcoratger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good, just some small comments! Thanks a lot!

slot=Slot(2),
latest_justified_slot=Slot(0),
latest_finalized_slot=Slot(0),
justified_slots=JustifiedSlots(data=[]).model_copy(update={"data": [Boolean(False)]}),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could have this no?

Suggested change
justified_slots=JustifiedSlots(data=[]).model_copy(update={"data": [Boolean(False)]}),
justified_slots=JustifiedSlots(data=[Boolean(False)]),

Copy link
Copy Markdown
Contributor Author

@uink45 uink45 Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for flagging this @tcoratger.

Regarding the model_copy simplification, I realised that when I replace it with JustifiedSlots(data=[Boolean(False)]) the constructor ends up converting the list into a tuple, while the actual state keeps it as a list. The equality check then fails when I run the state transition tests using uv run pytest:

AssertionError: State validation failed: justified_slots = data=[Boolean(False)], expected data=(Boolean(False),)

To fix this, I can submit a separate PR, however, I was wondering which approach should be taken as the fix could either go in BaseBitlist._coerce_and_validate (changing return tuple(...) to return list(...) to match what model_copy produces), or in validate_against_state. Do you have a preference on which approach?

Comment on lines +522 to +530
justified_slots=JustifiedSlots(data=[]).model_copy(
update={
"data": [
Boolean(True),
Boolean(False),
Boolean(False),
]
}
),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here we can simplify this and I guess this is the case for other places as well


Expected Behavior
-----------------
1. The two attestations do not merge because their attestation-data slots differ
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should remove this point because we never test it no?

Comment on lines +921 to +925
post=StateExpectation(
slot=Slot(2),
latest_justified_slot=Slot(1),
latest_finalized_slot=Slot(0),
),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we test justifications_validators and justified_slots as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @tcoratger, I have added post state checks for justifications_validators and justified_slots fields. This has been done for the tests that were missing them, as flagged in the review.

Comment on lines +970 to +974
post=StateExpectation(
slot=Slot(2),
latest_justified_slot=Slot(1),
latest_finalized_slot=Slot(0),
),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here why don't we test justifications_validators and justified_slots as well?

Comment on lines +1018 to +1022
post=StateExpectation(
slot=Slot(2),
latest_justified_slot=Slot(0),
latest_finalized_slot=Slot(0),
),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here? Why don't we test justifications_validators and justified_slots as well?

Comment on lines +1067 to +1071
post=StateExpectation(
slot=Slot(3),
latest_justified_slot=Slot(0),
latest_finalized_slot=Slot(0),
),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here we should probably check the other stuffs right?

Comment on lines +1152 to +1153
justified_slots=JustifiedSlots(data=[]).model_copy(
update={"data": [Boolean(True), Boolean(False), Boolean(False)]}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, no need to model copy

slot=Slot(6),
latest_justified_slot=Slot(5),
latest_finalized_slot=Slot(4),
justified_slots=JustifiedSlots(data=[]).model_copy(update={"data": [Boolean(True)]}),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Comment on lines +1376 to +1385
justified_slots=JustifiedSlots(data=[]).model_copy(
update={
"data": [
Boolean(True),
Boolean(False),
Boolean(False),
Boolean(True),
Boolean(False),
]
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

- Remove untested docstring claim about attestation-data slot merging
- Add justified_slots, justifications_roots, and justifications_validators
  assertions to split aggregations, odd threshold, and mismatched root tests
Copy link
Copy Markdown
Collaborator

@tcoratger tcoratger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot!

@tcoratger tcoratger merged commit 4114af9 into leanEthereum:main Apr 7, 2026
12 of 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.

2 participants