1313from lean_spec .types import Bytes32 , Uint64
1414
1515
16- class TestAttestationAggregation :
17- """Test proper attestation aggregation by common data ."""
16+ class TestAggregationBits :
17+ """Test aggregation bits functionality ."""
1818
1919 def test_reject_empty_aggregation_bits (self ) -> None :
2020 """Validate aggregated attestation must include at least one validator."""
2121 bits = AggregationBits (data = [False , False , False ])
2222 with pytest .raises (AssertionError , match = "at least one validator" ):
2323 bits .to_validator_indices ()
2424
25+ def test_to_validator_indices_single_bit (self ) -> None :
26+ """Test conversion with a single bit set."""
27+ bits = AggregationBits (data = [False , True , False ])
28+ indices = bits .to_validator_indices ()
29+ assert indices == [Uint64 (1 )]
30+
31+ def test_to_validator_indices_multiple_bits (self ) -> None :
32+ """Test conversion with multiple bits set."""
33+ bits = AggregationBits (data = [True , False , True , True , False ])
34+ indices = bits .to_validator_indices ()
35+ assert indices == [Uint64 (0 ), Uint64 (2 ), Uint64 (3 )]
36+
37+ def test_from_validator_indices_roundtrip (self ) -> None :
38+ """Test that from_validator_indices and to_validator_indices are inverses."""
39+ original_indices = [Uint64 (1 ), Uint64 (5 ), Uint64 (7 )]
40+ bits = AggregationBits .from_validator_indices (original_indices )
41+ recovered_indices = bits .to_validator_indices ()
42+ assert recovered_indices == original_indices
43+
44+
45+ class TestAggregatedAttestation :
46+ """Test aggregated attestation structure."""
47+
48+ def test_aggregated_attestation_structure (self ) -> None :
49+ """Test that aggregated attestation properly stores bits and data."""
50+ att_data = AttestationData (
51+ slot = Slot (5 ),
52+ head = Checkpoint (root = Bytes32 .zero (), slot = Slot (4 )),
53+ target = Checkpoint (root = Bytes32 .zero (), slot = Slot (3 )),
54+ source = Checkpoint (root = Bytes32 .zero (), slot = Slot (2 )),
55+ )
56+
57+ bits = AggregationBits .from_validator_indices ([Uint64 (2 ), Uint64 (7 )])
58+ agg = AggregatedAttestation (aggregation_bits = bits , data = att_data )
59+
60+ # Verify we can extract validator indices
61+ indices = agg .aggregation_bits .to_validator_indices ()
62+ assert set (indices ) == {Uint64 (2 ), Uint64 (7 )}
63+ assert agg .data == att_data
64+
65+ def test_aggregated_attestation_with_many_validators (self ) -> None :
66+ """Test aggregated attestation with many validators."""
67+ att_data = AttestationData (
68+ slot = Slot (10 ),
69+ head = Checkpoint (root = Bytes32 .zero (), slot = Slot (9 )),
70+ target = Checkpoint (root = Bytes32 .zero (), slot = Slot (8 )),
71+ source = Checkpoint (root = Bytes32 .zero (), slot = Slot (7 )),
72+ )
73+
74+ validator_ids = [Uint64 (i ) for i in [0 , 5 , 10 , 15 , 20 , 25 ]]
75+ bits = AggregationBits .from_validator_indices (validator_ids )
76+ agg = AggregatedAttestation (aggregation_bits = bits , data = att_data )
77+
78+ recovered = agg .aggregation_bits .to_validator_indices ()
79+ assert recovered == validator_ids
80+
81+
82+ class TestAggregateByData :
83+ """Test aggregation of plain attestations by common data."""
84+
2585 def test_aggregate_attestations_by_common_data (self ) -> None :
2686 """Test that attestations with same data are properly aggregated."""
27- # Create three attestations with two having common data
2887 att_data1 = AttestationData (
2988 slot = Slot (5 ),
3089 head = Checkpoint (root = Bytes32 .zero (), slot = Slot (4 )),
@@ -63,30 +122,6 @@ def test_aggregate_attestations_by_common_data(self) -> None:
63122 # Should contain only validator 5
64123 assert set (validator_ids2 ) == {Uint64 (5 )}
65124
66- def test_aggregate_attestations_sets_all_bits (self ) -> None :
67- """Test that aggregation sets all validator bits correctly."""
68- att_data = AttestationData (
69- slot = Slot (5 ),
70- head = Checkpoint (root = Bytes32 .zero (), slot = Slot (4 )),
71- target = Checkpoint (root = Bytes32 .zero (), slot = Slot (3 )),
72- source = Checkpoint (root = Bytes32 .zero (), slot = Slot (2 )),
73- )
74-
75- attestations = [
76- Attestation (validator_id = Uint64 (2 ), data = att_data ),
77- Attestation (validator_id = Uint64 (7 ), data = att_data ),
78- Attestation (validator_id = Uint64 (10 ), data = att_data ),
79- ]
80-
81- aggregated = AggregatedAttestation .aggregate_by_data (attestations )
82-
83- assert len (aggregated ) == 1
84- validator_ids = aggregated [0 ].aggregation_bits .to_validator_indices ()
85-
86- # Should have all three validators
87- assert len (validator_ids ) == 3
88- assert set (validator_ids ) == {Uint64 (2 ), Uint64 (7 ), Uint64 (10 )}
89-
90125 def test_aggregate_empty_attestations (self ) -> None :
91126 """Test aggregation with no attestations."""
92127 aggregated = AggregatedAttestation .aggregate_by_data ([])
@@ -108,35 +143,3 @@ def test_aggregate_single_attestation(self) -> None:
108143 assert len (aggregated ) == 1
109144 validator_ids = aggregated [0 ].aggregation_bits .to_validator_indices ()
110145 assert validator_ids == [Uint64 (5 )]
111-
112-
113- class TestDuplicateAttestationDataValidation :
114- """Test validation that blocks don't contain duplicate AttestationData."""
115-
116- def test_duplicate_attestation_data_detection (self ) -> None :
117- """Ensure conversion to plain attestations preserves duplicates."""
118- att_data = AttestationData (
119- slot = Slot (1 ),
120- head = Checkpoint (root = Bytes32 .zero (), slot = Slot (0 )),
121- target = Checkpoint (root = Bytes32 .zero (), slot = Slot (0 )),
122- source = Checkpoint (root = Bytes32 .zero (), slot = Slot (0 )),
123- )
124-
125- from lean_spec .subspecs .containers .attestation import AggregatedAttestation
126- from lean_spec .subspecs .containers .attestation .types import AggregationBits
127-
128- agg1 = AggregatedAttestation (
129- aggregation_bits = AggregationBits (data = [False , True ]),
130- data = att_data ,
131- )
132- agg2 = AggregatedAttestation (
133- aggregation_bits = AggregationBits (data = [False , True , True ]),
134- data = att_data ,
135- )
136-
137- plain = [plain_att for aggregated in (agg1 , agg2 ) for plain_att in aggregated .to_plain ()]
138-
139- # Expect 2 plain attestations (because validator 1 is common in agg1 and agg2)
140- # validator 1 and validator 2 are the only unique validators in the attestations
141- assert len (set (plain )) == 2
142- assert all (att .data == att_data for att in plain )
0 commit comments