|
5 | 5 | import pytest |
6 | 6 |
|
7 | 7 | from lean_spec.subspecs.ssz.merkleization import ( |
| 8 | + _ZERO_HASHES, |
| 9 | + _merkleize_efficient, |
8 | 10 | _zero_tree_root, |
9 | 11 | merkleize, |
10 | 12 | mix_in_length, |
@@ -148,3 +150,57 @@ def test_zero_tree_root_internal() -> None: |
148 | 150 | assert _zero_tree_root(4) == Z[2] |
149 | 151 | assert _zero_tree_root(8) == Z[3] |
150 | 152 | assert _zero_tree_root(16) == Z[4] |
| 153 | + |
| 154 | + |
| 155 | +def test_zero_tree_root_fallback_beyond_precomputed_depth() -> None: |
| 156 | + """Tests the fallback path for trees deeper than the pre-computed cache (depth >= 65).""" |
| 157 | + # _ZERO_HASHES has 65 entries (indices 0..64). |
| 158 | + # width_pow2 = 2**65 gives depth = 65, which equals len(_ZERO_HASHES), |
| 159 | + # triggering the fallback that hashes upward from _ZERO_HASHES[-1]. |
| 160 | + width_pow2 = 2**65 |
| 161 | + result = _zero_tree_root(width_pow2) |
| 162 | + |
| 163 | + # The fallback computes one extra hash step beyond the last cached value. |
| 164 | + # depth=65, len(_ZERO_HASHES)=65, so range(65 - 65 + 1) = range(1) -> one iteration. |
| 165 | + expected = h(_ZERO_HASHES[-1], _ZERO_HASHES[-1]) |
| 166 | + assert result == expected |
| 167 | + |
| 168 | + |
| 169 | +def test_zero_tree_root_fallback_two_steps_beyond_cache() -> None: |
| 170 | + """Tests the fallback path with depth two steps beyond the pre-computed cache.""" |
| 171 | + # width_pow2 = 2**66 gives depth = 66, requiring two hash steps beyond cache. |
| 172 | + width_pow2 = 2**66 |
| 173 | + result = _zero_tree_root(width_pow2) |
| 174 | + |
| 175 | + step1 = h(_ZERO_HASHES[-1], _ZERO_HASHES[-1]) |
| 176 | + expected = h(step1, step1) |
| 177 | + assert result == expected |
| 178 | + |
| 179 | + |
| 180 | +def test_merkleize_efficient_secondary_loop() -> None: |
| 181 | + """ |
| 182 | + Tests the secondary reduction loop in efficient merkleization. |
| 183 | +
|
| 184 | + When called directly with more chunks than width (not possible through merkleize), |
| 185 | + the main loop exits with multiple remaining nodes, triggering the secondary reduction loop. |
| 186 | + """ |
| 187 | + # 4 chunks with width=2: main loop exits after subtree_size reaches 2, |
| 188 | + # leaving level=[h(c0,c1), h(c2,c3)] with len=2, triggering secondary loop. |
| 189 | + result = _merkleize_efficient([c[0], c[1], c[2], c[3]], width=2) |
| 190 | + assert result == h(h(c[0], c[1]), h(c[2], c[3])) |
| 191 | + |
| 192 | + |
| 193 | +def test_merkleize_efficient_secondary_loop_odd_nodes() -> None: |
| 194 | + """ |
| 195 | + Tests the secondary reduction loop with an odd number of remaining nodes. |
| 196 | +
|
| 197 | + Exercises the zero-padding branch when a node has no right sibling. |
| 198 | + """ |
| 199 | + # 3 chunks with width=1: main loop never runs (subtree_size=1 >= width=1), |
| 200 | + # so level stays as [c0, c1, c2] with len=3, triggering secondary loop. |
| 201 | + # |
| 202 | + # Secondary loop iteration 1: pairs (c0,c1)->h01, c2 has no right sibling |
| 203 | + # -> h(c2, _zero_tree_root(1)) = h(c2, Z[0]). Level=[h01, h2z], subtree_size=2. |
| 204 | + # Secondary loop iteration 2: pairs -> h(h01, h2z). Level=[result]. |
| 205 | + result = _merkleize_efficient([c[0], c[1], c[2]], width=1) |
| 206 | + assert result == h(h(c[0], c[1]), h(c[2], Z[0])) |
0 commit comments