Emit `node`'s plan entries; return the index of the entry representing it.
(node: &AggNode, arity: usize, ops: &mut Vec<FoldOp>)
| 365 | /// *collapsed* so each agg call folds up to `arity` whole subtrees (never |
| 366 | /// splitting a subtree across calls), keeping the agg-proof count ~`N/(arity-1)` |
| 367 | /// — a strict binary fold would be ~`N` agg proofs, dominating cost. Children of |
| 368 | /// each agg are emitted in shard order so the subject merkle-fold matches the |
| 369 | /// guest's left-associative join. |
| 370 | pub fn agg_plan(tree: &AggNode, arity: usize) -> Vec<FoldOp> { |
| 371 | let arity = arity.max(2); |
| 372 | let mut ops: Vec<FoldOp> = Vec::new(); |
| 373 | build_plan(tree, arity, &mut ops); |
| 374 | ops |
| 375 | } |
| 376 | |
| 377 | /// Emit `node`'s plan entries; return the index of the entry representing it. |
| 378 | fn build_plan(node: &AggNode, arity: usize, ops: &mut Vec<FoldOp>) -> usize { |
| 379 | match node { |
| 380 | AggNode::Leaf(id) => { |
| 381 | ops.push(FoldOp::Leaf(*id)); |
| 382 | ops.len() - 1 |
| 383 | }, |
| 384 | AggNode::Internal(l, r) => { |
| 385 | // Collapse the binary subtree into up to `arity` child subtrees: start |
| 386 | // from the two halves and repeatedly split the *largest* still-internal |
| 387 | // frontier node until we have `arity` children or all are leaves. This |
| 388 | // keeps tightly-coupled siblings together while bounding the fan-in. |
| 389 | let mut frontier: Vec<&AggNode> = vec![l, r]; |
| 390 | while frontier.len() < arity { |
| 391 | let largest = frontier |
| 392 | .iter() |
| 393 | .enumerate() |
| 394 | .filter(|(_, nd)| matches!(nd, AggNode::Internal(_, _))) |
| 395 | .max_by_key(|(_, nd)| nd.num_leaves()) |
| 396 | .map(|(i, _)| i); |
| 397 | let Some(i) = largest else { break }; // all leaves: cannot expand |
| 398 | let AggNode::Internal(l, r) = frontier.swap_remove(i) else { |
| 399 | unreachable!() |
| 400 | }; |
| 401 | frontier.push(l); |
| 402 | frontier.push(r); |
| 403 | } |
| 404 | // Shard order keeps the merkle subject-fold left-associative. |
no test coverage detected