Skip to content

Commit 61981b3

Browse files
authored
fix(test-driver): accept leanSpec mocked aggregate proofs (lambdaclass#488)
## Problem ethlambda's Hive test-driver (`crates/net/rpc/src/test_driver.rs`) fails devnet5 `lean-spec-tests-fork-choice` because it does not honor leanSpec's MOCKED crypto mode. leanSpec fork_choice fixtures generated with `proofSetting: 0` carry PLACEHOLDER aggregate proofs: every proof blob is the sentinel bytes `\x00MOCKED-AGGREGATION-PROOF\x00` followed by a sha256 fingerprint, NOT a real proof. leanSpec's own verifier (`packages/testing/src/consensus_testing/crypto_mode.py`) accepts any sentinel-prefixed blob unchecked and only falls through to the real verifier for genuine (`proofSetting=1`) proofs. A conformant client must do the same. The driver routed every `gossipAggregatedAttestation` step through the verifying `store::on_gossip_aggregated_attestation`, which decompresses the proof and fails deserialization on a mock blob (`VerificationError::DeserializationFailed`). The driver then reported `accepted=false` where the fixture expects `valid=true`, producing a Hive "acceptance mismatch". This accounts for ~24 fork-choice failures. ## Fix Detect the `MOCK_PROOF_PREFIX` sentinel before constructing the aggregate and route mocked proofs through the non-verifying path `store::on_gossip_aggregated_attestation_without_verification`. That path still runs all NON-crypto validation (`validate_attestation_data`, participant/index checks), so validation-rejection fixtures still reject correctly. Genuine (`proofSetting=1`) proofs continue to run the real verifier unchanged. ## Tests New `crates/net/rpc/tests/test_driver_mocked_proofs.rs` replays real fixtures through the router exactly as Hive does (init + per-step POST, comparing `accepted` to each step's `valid` and `snapshot.headSlot` to `checks.headSlot`), gated on fixtures being present: - `test_valid_gossip_aggregated_attestation.json` (proofSetting=0): the mocked aggregated-attestation step is now ACCEPTED; replay finds no divergence. - `test_aggregated_attestation_source_after_target_rejected.json` (proofSetting=0): the validation-rejection step is still REJECTED, proving only CRYPTO verification was skipped, not validation. `cargo test -p ethlambda-rpc --release --test test_driver_mocked_proofs` and the existing `test_driver_e2e` both pass; `make fmt` and `make lint` are clean. ## Note This is 1 of 2 independent PRs fixing the devnet5 fork-choice Hive suite; the other folds block-borne attestation votes. Both touch `apply_step` in the same file, so a trivial rebase may be needed when the second merges.
1 parent 38182c8 commit 61981b3

1 file changed

Lines changed: 19 additions & 1 deletion

File tree

‎crates/net/rpc/src/test_driver.rs‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ use tracing::debug;
5858
/// of `"1"`, `"true"`, or `"yes"` (case-insensitive) enables the driver.
5959
pub const TEST_DRIVER_ENV: &str = "HIVE_LEAN_TEST_DRIVER";
6060

61+
/// Sentinel prefixing every placeholder proof leanSpec's mocked prover emits
62+
/// (`proofSetting: 0` fixtures). Matches `MOCK_PROOF_PREFIX` in leanSpec's
63+
/// `packages/testing/src/consensus_testing/crypto_mode.py`. Proofs carrying it
64+
/// are accepted without cryptographic verification, mirroring leanSpec's mocked
65+
/// verifier; genuine (`proofSetting: 1`) proofs still run the real verifier.
66+
const MOCK_PROOF_PREFIX: &[u8] = b"\x00MOCKED-AGGREGATION-PROOF\x00";
67+
6168
/// Whether the supplied env-var value should activate the driver.
6269
fn parse_truthy_env_value(value: &str) -> bool {
6370
matches!(
@@ -413,14 +420,25 @@ fn apply_step(store: &mut Store, step: ForkChoiceStep) -> Result<(), String> {
413420
.ok_or_else(|| "gossipAggregatedAttestation step missing proof".to_string())?;
414421
let participants: EthAggregationBits = proof.participants.into();
415422
let proof_bytes: Vec<u8> = proof.proof.into();
423+
// leanSpec's mocked prover (proofSetting=0) emits placeholder proofs
424+
// prefixed with MOCK_PROOF_PREFIX and expects verifiers to accept them
425+
// unchecked. Route those through the non-verifying path; genuine proofs
426+
// still run the real verifier.
427+
let is_mocked = proof_bytes.starts_with(MOCK_PROOF_PREFIX);
416428
let proof_data = ByteList512KiB::try_from(proof_bytes)
417429
.map_err(|err| format!("aggregated proof data too large: {err:?}"))?;
418430
let data: ethlambda_types::attestation::AttestationData = att.data.into();
419431
let aggregated = SignedAggregatedAttestation {
420432
proof: SingleMessageAggregate::new(participants, proof_data),
421433
data,
422434
};
423-
store::on_gossip_aggregated_attestation(store, aggregated).map_err(|e| e.to_string())
435+
if is_mocked {
436+
store::on_gossip_aggregated_attestation_without_verification(store, aggregated)
437+
.map_err(|e| e.to_string())
438+
} else {
439+
store::on_gossip_aggregated_attestation(store, aggregated)
440+
.map_err(|e| e.to_string())
441+
}
424442
}
425443
// `checks`-only steps are no-ops here: the simulator validates them
426444
// against the snapshot returned alongside this response.

0 commit comments

Comments
 (0)