Skip to content

fix: correct midpoint level so odd-depth uploads don't revert#9

Merged
mickvandijke merged 2 commits intomainfrom
fix/odd-depth-pool-count-revert
Apr 27, 2026
Merged

fix: correct midpoint level so odd-depth uploads don't revert#9
mickvandijke merged 2 commits intomainfrom
fix/odd-depth-pool-count-revert

Conversation

@grumbach
Copy link
Copy Markdown
Contributor

Summary

  • Fixes WrongPoolCount reverts at the on-chain payForMerkleTree call for files producing odd-depth merkle trees (chunk counts in the bands 5-8, 17-32, 65-128). Production observed it as WrongPoolCount(16, 8) for files in the 65-128 chunk band.
  • Root cause: MerklePaymentLib.expectedRewardPools(depth) = 2^ceil(depth/2) (Solidity), but the client computed midpoints at level ceil(depth/2) from the leaves. ant_merkle::get_nodes_at_level numbers levels from the leaves up (L=0 leaves, L=depth root), so picking L = ceil(d/2) returns 2^floor(d/2) nodes — half the count for odd depths.
  • Fix: set midpoint_level = floor(depth/2), which yields 2^(d - floor(d/2)) = 2^ceil(d/2) nodes — matches the contract for both even and odd depths. expected_reward_pools is unchanged (already 2^ceil(d/2) since fix: payment hardening -- pricing, panics, external signer #5).
  • Adds five regression tests that exhaustively cover every supported leaf count, including the production failure mode and the full verifier path.

Why the previous attempt missed this

PR #5's commit f4cdd45 ("align pool count formula with Solidity contract (floor->ceil)") flipped both expected_reward_pools and midpoint_level to ceil in lockstep. That fixes expected_reward_pools (correct) but leaves the bug in midpoint_level because ant_merkle levels are counted from the leaves — flipping floor to ceil there moves the cut closer to the root and halves the pool count for odd depths. The doc comment now spells this out so the next reader doesn't repeat the mistake.

Test plan

  • cargo test --lib — 16 passed (5 new merkle regression tests + 11 pre-existing)
  • cargo clippy --all-targets -- -D warnings clean
  • cargo fmt clean
  • Verify upload of a file in each failing chunk band (e.g. 25 MiB, 100 MiB, 400 MiB) succeeds against the live testnet once this lands and clients pick it up

Risk

  • The fix is one line (depth.div_ceil(2) -> depth / 2) in a non-public function. No wire-format change, no contract change.
  • Even-depth trees are unaffected: floor(d/2) == ceil(d/2) for even d.
  • midpoint_proof_depth is unchanged and remains ceil(d/2) — that is the path length from level floor(d/2) to the root, which is d - floor(d/2) = ceil(d/2). The every_midpoint_proof_verifies_for_all_depths and verify_merkle_proof_accepts_all_depths tests confirm the proof shape stays consistent post-fix.

grumbach and others added 2 commits April 27, 2026 10:09
MerklePaymentLib.expectedRewardPools(depth) = 2^ceil(depth/2), but the
client was computing midpoints at level ceil(depth/2) from the leaves.
ant_merkle::get_nodes_at_level numbers levels from the leaves up
(L=0 leaves, L=depth root) so a tree of 2^d leaves has 2^(d-L) nodes
at level L. With L=ceil(d/2) the client produced 2^floor(d/2) pools,
half the count for odd depths, and the on-chain payForMerkleTree call
reverted with WrongPoolCount(expected, actual).

Setting the midpoint level to floor(depth/2) yields 2^ceil(depth/2)
midpoints, matching the contract for both even and odd depths.

Observed in production as WrongPoolCount(16, 8): any file producing
65-128 chunks (depth 7) failed at payment regardless of network state.
Files with chunk counts in 5-8 (depth 3) and 17-32 (depth 5) hit the
same bug.

Adds five regression tests:
- reward_candidate_count_matches_contract_for_all_depths (every leaf count up to MAX_LEAVES)
- depth_seven_produces_sixteen_pools_not_eight (the production failure)
- depth_one_produces_two_pools_not_one (smallest odd-depth case)
- every_midpoint_proof_verifies_for_all_depths (proofs still verify post-fix)
- verify_merkle_proof_accepts_all_depths (full production verifier path)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mickvandijke mickvandijke force-pushed the fix/odd-depth-pool-count-revert branch from ed36ae0 to 53fcb89 Compare April 27, 2026 08:12
@mickvandijke mickvandijke merged commit 225acbb into main Apr 27, 2026
7 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