feat(cli): benchmark block building with real XMSS and leanVM crypto - #611
feat(cli): benchmark block building with real XMSS and leanVM crypto#611pablodeymo wants to merge 2 commits into
Conversation
…time its phases The sign, wrap and merge steps that turn a built block into a SignedBlock lived only inline in BlockChainServer::propose_block, so nothing outside the running node could exercise or time them. seal_block is that code lifted verbatim behind a SealError enum; propose_block calls it and keeps its logging and failure counter on Err. Each step is observed on the block-proposal phase histogram under three new labels (sign_proposer, wrap_proposer, merge_type2) listed in BLOCK_PROPOSAL_SEAL_PHASES, alongside the existing build phases. Together they cover the same span as lean_block_building_time_seconds, so the per-phase breakdown now accounts for the whole build.
`ethlambda benchmark synthetic` no longer requires --mock-crypto. Without it the run derives an attestation and a proposal XMSS key per validator from the seed (keygen is sized to the slots the run signs, so it costs seconds, and --key-cache <dir> stores the keys for reruns), puts the real pubkeys in the synthetic genesis, has every validator sign each slot's attestation data through the production KeyManager, aggregates each participant group into a real leanVM type-1 proof, and seals the built block with seal_block. The measured span is build plus seal, exactly the node's lean_block_building_time_seconds boundary, and the three seal phases appear as columns. Every sealed block is imported through the verifying on_block, so an invalid proof fails the run rather than producing a report about invalid blocks. Two costs outside the span are reported anyway, since they are real crypto: `aggregate` (the aggregator-side signing and type-1 aggregation that produces the slot's pool entries) and `import` (which now includes type-2 verification). Same seed and parameters still reproduce the same block roots: XMSS signing is deterministic and the keys are seed-derived. --mock-crypto keeps the sub-second path CI runs; it skips the seal, so its report carries only the build phases. Verified locally with two validators: keys generated in 6.8s, ~0.1s per slot of type-1 aggregation, 0.5-1.3s type-2 merges, 20ms verified imports, identical roots across a rerun that loaded the keys from the cache, and a real recursive compaction under --enable-proposer-aggregation --proofs-per-data 2.
🤖 Kimi Code ReviewI'll review this PR which adds real XMSS/leanVM cryptography to the benchmark harness, replacing the previous mock-only mode. Let me analyze the changes systematically. Overall AssessmentThis is a well-structured PR that extends the benchmark to support real cryptographic operations. The code is generally clean, but I've identified several issues ranging from minor to potentially serious. Critical Issues1. Potential Panic on Out-of-Bounds Access in
|
| Priority | Item | Location | Issue |
|---|---|---|---|
| Critical | 2 | benchmark/mod.rs:179 |
Slot/epoch confusion in key window |
| Critical | 6 | block_builder.rs:940 |
expect can panic on slot conversion |
| High | 1 | corpus.rs:74 |
Unbounded indexing into genesis_pubkeys |
| High | 8 | benchmark/mod.rs:179 |
total_slots + 1 can overflow |
| High | 3 | keys.rs:183 |
Weak RNG seed construction |
| Security | 4 | keys.rs:89-96 |
Unauthenticated secret key cache |
| Security | 5 | keys.rs:58-60 |
Cache directory race condition |
| Medium | 14 | keys.rs:16 |
size_of assumption on pubkey type |
| Medium | 11 | block_builder.rs:900 |
String-based error variants |
| Low | 9 | corpus.rs |
Redundant hash_tree_root calls |
| Low | 13 | keys.rs:247 |
Test cleanup not guaranteed on panic |
The most important fix is Item 2 (slot/epoch confusion), as it fundamentally misunderstands the leansig API and could cause massive performance degradation or incorrect key generation.
Automated review by Kimi (Moonshot AI) · kimi-k2.6 · custom prompt
🤖 Claude Code ReviewReview: Benchmark block building with real XMSS and leanVM cryptoOverall this is a well-scoped, well-tested change. The A few points worth a look, none blocking:
Nothing found in the fork-choice/state-transition/attestation-processing logic itself, since this PR only touches the benchmark harness and the (behavior-preserving) Automated review by Claude (Anthropic) · sonnet · custom prompt |
🤖 Codex Code ReviewAutomated review by OpenAI Codex |
🗒️ Description / Motivation
ethlambda benchmark syntheticonly ran with--mock-crypto, so it measured selection, compaction and the state transition but none of the signing or aggregation that dominates a real proposal. This makes real crypto the default and measures the proposer's seal.Without
--mock-cryptothe run derives an attestation and a proposal XMSS key per validator from the seed (sized to the slots the run signs, so keygen costs seconds;--key-cache <dir>stores them for reruns), puts the real pubkeys in the synthetic genesis, has every validator sign each slot's attestation data through the productionKeyManager, aggregates each participant group into a real leanVM type-1 proof, and seals the built block withseal_block. The measured span is build plus seal, exactly the node'slean_block_building_time_secondsboundary.What Changed
crates/blockchain/src/block_builder.rsseal_block+SealError, lifted verbatim frompropose_block; observessign_proposer,wrap_proposer,merge_type2crates/blockchain/src/lib.rspropose_blockcallsseal_block, same logging and failure counter on errorcrates/blockchain/src/metrics.rsBLOCK_PROPOSAL_SEAL_PHASES; the histogram now spans the same work aslean_block_building_time_secondsbin/ethlambda/src/benchmark/keys.rs--key-cache <dir>keyed by leansig rev/seed/index/windowbin/ethlambda/src/benchmark/corpus.rsCryptoMode::{Mock, Real}: real genesis pubkeys, real type-1 proofs signed through the productionKeyManagerbin/ethlambda/src/benchmark/mod.rson_blockimport in real modebin/ethlambda/src/benchmark/report.rsaggregateandimportcolumns (outside the span)docs/benchmarking.md,docs/metrics.mdCorrectness / Behavior Guarantees
propose_blockbehavior is unchanged; the seal is the same code behind a function.make benchrun; it skips the seal since there are no keys, so its report carries only the build phases.on_block, so an invalid proof fails the run instead of producing a report about invalid blocks.phaselabel values onlean_block_proposal_attestation_build_phase_seconds.Tests Added / Run
keys.rs: determinism per seed and role; cache round-trip that signs through theKeyManager.corpus.rs:#[ignore = "too slow"]test that a seeded proof passesverify_aggregated_signature(passes locally).--enable-proposer-aggregation --proofs-per-data 2exercised a real recursive compaction (compact= 0.56s).schema_version == 1, one sample per iteration, build phases only).Related Issues / PRs
✅ Verification Checklist
make fmt— cleanmake lint(clippy with-D warnings) — cleanmake test(cargo test --workspace --profile release-fast) — all passing on the latest leanSpec fixtures