mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 20:57:10 +00:00
ef_tests: wire fork-choice compliance suites
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
Michael Sproul
parent
7148bfcdd1
commit
8eed94e64a
@@ -1090,7 +1090,7 @@ impl ProtoArray {
|
|||||||
///
|
///
|
||||||
/// Returns the set of node indices on viable branches — those with at least
|
/// Returns the set of node indices on viable branches — those with at least
|
||||||
/// one leaf descendant with correct justified/finalized checkpoints.
|
/// one leaf descendant with correct justified/finalized checkpoints.
|
||||||
fn get_filtered_block_tree<E: EthSpec>(
|
pub(crate) fn get_filtered_block_tree<E: EthSpec>(
|
||||||
&self,
|
&self,
|
||||||
start_index: usize,
|
start_index: usize,
|
||||||
current_slot: Slot,
|
current_slot: Slot,
|
||||||
|
|||||||
@@ -1080,6 +1080,51 @@ impl ProtoArrayForkChoice {
|
|||||||
.map(|node| node.weight())
|
.map(|node| node.weight())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the leaves of the filtered block tree (rooted at `justified_root`) along with
|
||||||
|
/// their weights — i.e. roots that are viable for head and have no descendant that is also
|
||||||
|
/// viable for head. Mirrors the spec's `viable_for_head_roots_and_weights` check.
|
||||||
|
pub fn filtered_block_tree_leaves_and_weights<E: EthSpec>(
|
||||||
|
&self,
|
||||||
|
justified_root: &Hash256,
|
||||||
|
current_slot: Slot,
|
||||||
|
justified_checkpoint: Checkpoint,
|
||||||
|
finalized_checkpoint: Checkpoint,
|
||||||
|
) -> Result<Vec<(Hash256, u64)>, String> {
|
||||||
|
let start_index = self
|
||||||
|
.proto_array
|
||||||
|
.indices
|
||||||
|
.get(justified_root)
|
||||||
|
.copied()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"filtered_block_tree_leaves_and_weights: justified node \
|
||||||
|
{justified_root:?} unknown"
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let viable = self.proto_array.get_filtered_block_tree::<E>(
|
||||||
|
start_index,
|
||||||
|
current_slot,
|
||||||
|
justified_checkpoint,
|
||||||
|
finalized_checkpoint,
|
||||||
|
);
|
||||||
|
let mut leaves = Vec::with_capacity(viable.len());
|
||||||
|
for &i in &viable {
|
||||||
|
let has_viable_child = viable
|
||||||
|
.iter()
|
||||||
|
.any(|&j| self.proto_array.nodes.get(j).and_then(|n| n.parent()) == Some(i));
|
||||||
|
if has_viable_child {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let node = self
|
||||||
|
.proto_array
|
||||||
|
.nodes
|
||||||
|
.get(i)
|
||||||
|
.ok_or_else(|| format!("invalid viable node index {i}"))?;
|
||||||
|
leaves.push((node.root(), node.weight()));
|
||||||
|
}
|
||||||
|
Ok(leaves)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the payload status of the head node based on accumulated weights and tiebreaker.
|
/// Returns the payload status of the head node based on accumulated weights and tiebreaker.
|
||||||
///
|
///
|
||||||
/// See `ProtoArray` documentation.
|
/// See `ProtoArray` documentation.
|
||||||
|
|||||||
296
scripts/compliance-fc-report.sh
Executable file
296
scripts/compliance-fc-report.sh
Executable file
@@ -0,0 +1,296 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# scripts/compliance-fc-report.sh
|
||||||
|
#
|
||||||
|
# Run the consensus-specs fork-choice compliance suites against this branch and
|
||||||
|
# print an aggregated pass/fail summary. Test-only — no production code changes.
|
||||||
|
# See --help for data-source options.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
# ---- defaults (overridable via flags or env) ---------------------------------
|
||||||
|
|
||||||
|
PRESET="${COMPLIANCE_FC_PRESET:-minimal}"
|
||||||
|
DIR="${COMPLIANCE_FC_DIR:-}"
|
||||||
|
TARBALL="${COMPLIANCE_FC_TARBALL:-}"
|
||||||
|
URL="${COMPLIANCE_FC_URL:-}"
|
||||||
|
RUN_ID="${COMPLIANCE_FC_RUN_ID:-}"
|
||||||
|
CACHE_ROOT="${COMPLIANCE_FC_CACHE_DIR:-/var/tmp/compliance_fc_cache}"
|
||||||
|
SUITE_FILTER="${COMPLIANCE_FC_SUITE:-}"
|
||||||
|
|
||||||
|
ALL_SUITES=(
|
||||||
|
attester_slashing_test
|
||||||
|
block_cover_test
|
||||||
|
block_tree_test
|
||||||
|
block_weight_test
|
||||||
|
invalid_message_test
|
||||||
|
shuffling_test
|
||||||
|
)
|
||||||
|
ALL_FORKS=(fulu gloas)
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Run the fork-choice compliance suites and print a pass/fail report.
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
scripts/compliance-fc-report.sh [options] [-- cargo_args...]
|
||||||
|
|
||||||
|
DATA SOURCE (first non-empty wins)
|
||||||
|
--dir PATH Use a pre-extracted tree at PATH (must contain tests/).
|
||||||
|
--tarball PATH Use a local .tar.gz; extracted to cache on first use.
|
||||||
|
--url URL Download tarball via curl; cached + extracted.
|
||||||
|
--run-id ID Pin to a consensus-specs run id; download via gh.
|
||||||
|
(default) Resolve the latest successful run of the consensus-specs
|
||||||
|
"Compliance Tests" workflow on master via gh.
|
||||||
|
|
||||||
|
OTHER OPTIONS
|
||||||
|
--preset NAME Preset: minimal or mainnet (default: minimal).
|
||||||
|
Only minimal currently ships compliance data.
|
||||||
|
--suite NAME Run only one suite (e.g. block_tree_test). Repeatable
|
||||||
|
via comma-separated list.
|
||||||
|
--cache-dir PATH Cache root (default: /var/tmp/compliance_fc_cache).
|
||||||
|
-h, --help Print this help and exit.
|
||||||
|
|
||||||
|
ENVIRONMENT (each flag has a matching env var; the flag wins if both are set)
|
||||||
|
COMPLIANCE_FC_PRESET, COMPLIANCE_FC_DIR, COMPLIANCE_FC_TARBALL,
|
||||||
|
COMPLIANCE_FC_URL, COMPLIANCE_FC_RUN_ID, COMPLIANCE_FC_CACHE_DIR,
|
||||||
|
COMPLIANCE_FC_SUITE
|
||||||
|
GITHUB_TOKEN Required only for --run-id and the default auto-fetch path
|
||||||
|
(GitHub Actions artifact downloads return 403 to anonymous
|
||||||
|
requests, even on public repos). Use --tarball or --url to
|
||||||
|
avoid the token entirely.
|
||||||
|
|
||||||
|
Anything after '--' is forwarded verbatim to 'cargo test'.
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
# Auto-fetch latest (needs token)
|
||||||
|
GITHUB_TOKEN=... scripts/compliance-fc-report.sh
|
||||||
|
|
||||||
|
# Use a manually-downloaded tarball — no token, no gh
|
||||||
|
scripts/compliance-fc-report.sh --tarball ~/Downloads/small.tar.gz
|
||||||
|
|
||||||
|
# Pull the artifact through a public mirror via curl — no token
|
||||||
|
scripts/compliance-fc-report.sh --url https://example.org/small.tar.gz
|
||||||
|
|
||||||
|
# Re-use an already-extracted tree
|
||||||
|
scripts/compliance-fc-report.sh --dir /var/tmp/compliance_fc_root
|
||||||
|
|
||||||
|
# Run only one suite
|
||||||
|
scripts/compliance-fc-report.sh --tarball ./small.tar.gz --suite block_tree_test
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- argument parsing --------------------------------------------------------
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
--preset) PRESET="$2"; shift 2 ;;
|
||||||
|
--dir) DIR="$2"; shift 2 ;;
|
||||||
|
--tarball) TARBALL="$2"; shift 2 ;;
|
||||||
|
--url) URL="$2"; shift 2 ;;
|
||||||
|
--run-id) RUN_ID="$2"; shift 2 ;;
|
||||||
|
--suite) SUITE_FILTER="$2"; shift 2 ;;
|
||||||
|
--cache-dir) CACHE_ROOT="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
-*) echo "error: unknown option: $1" >&2; usage >&2; exit 1 ;;
|
||||||
|
*) echo "error: unexpected positional arg: $1 (use -- to forward to cargo)" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
CARGO_EXTRA_ARGS=("$@")
|
||||||
|
|
||||||
|
case "$PRESET" in
|
||||||
|
minimal|mainnet) ;;
|
||||||
|
*) echo "error: --preset must be minimal or mainnet (got: $PRESET)" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "$PRESET" == "mainnet" ]]; then
|
||||||
|
echo "error: the consensus-specs Compliance Tests workflow ships only minimal preset" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Resolve repo root (script may be invoked from any cwd).
|
||||||
|
REPO_ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." &> /dev/null && pwd)
|
||||||
|
TESTS_DST="${REPO_ROOT}/testing/ef_tests/consensus-spec-tests"
|
||||||
|
|
||||||
|
# ---- data source resolution --------------------------------------------------
|
||||||
|
|
||||||
|
extract_into() {
|
||||||
|
# extract_into <tarball> <cache-key>; sets DIR to the cache target.
|
||||||
|
local tarball="$1" key="$2" target="$CACHE_ROOT/$key"
|
||||||
|
if [[ -d "$target/tests" ]]; then
|
||||||
|
echo "Reusing cached compliance data at $target"
|
||||||
|
else
|
||||||
|
mkdir -p "$target"
|
||||||
|
echo "Extracting $tarball -> $target..."
|
||||||
|
tar -xzf "$tarball" -C "$target"
|
||||||
|
fi
|
||||||
|
DIR="$target"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_token() {
|
||||||
|
: "${GITHUB_TOKEN:?required for gh artifact download (use --tarball or --url to avoid)}"
|
||||||
|
command -v gh >/dev/null || {
|
||||||
|
echo "error: gh CLI required for auto-fetch (brew install gh, or use --tarball/--url/--dir)" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -n "$DIR" ]]; then
|
||||||
|
: # use as-is
|
||||||
|
elif [[ -n "$TARBALL" ]]; then
|
||||||
|
[[ -f "$TARBALL" ]] || { echo "error: tarball not found: $TARBALL" >&2; exit 1; }
|
||||||
|
key="tarball-$(shasum -a 256 "$TARBALL" | awk '{print $1}')"
|
||||||
|
extract_into "$TARBALL" "$key"
|
||||||
|
elif [[ -n "$URL" ]]; then
|
||||||
|
command -v curl >/dev/null || { echo "error: curl required for --url" >&2; exit 1; }
|
||||||
|
key="url-$(printf '%s' "$URL" | shasum -a 256 | awk '{print $1}')"
|
||||||
|
target="$CACHE_ROOT/$key"
|
||||||
|
if [[ -d "$target/tests" ]]; then
|
||||||
|
echo "Reusing cached compliance data at $target"
|
||||||
|
DIR="$target"
|
||||||
|
else
|
||||||
|
tmpfile="$(mktemp)"
|
||||||
|
trap 'rm -f "$tmpfile"' EXIT
|
||||||
|
echo "Downloading $URL..."
|
||||||
|
curl -fL --output "$tmpfile" "$URL"
|
||||||
|
extract_into "$tmpfile" "$key"
|
||||||
|
rm -f "$tmpfile"
|
||||||
|
trap - EXIT
|
||||||
|
fi
|
||||||
|
elif [[ -n "$RUN_ID" || -n "${GITHUB_TOKEN:-}" ]]; then
|
||||||
|
require_token
|
||||||
|
if [[ -z "$RUN_ID" ]]; then
|
||||||
|
# 261432977 = "Compliance Tests" workflow on ethereum/consensus-specs
|
||||||
|
echo "Resolving latest successful Compliance Tests run on master..."
|
||||||
|
RUN_ID=$(gh api \
|
||||||
|
'repos/ethereum/consensus-specs/actions/workflows/261432977/runs?branch=master&status=success&per_page=1' \
|
||||||
|
--jq '.workflow_runs[0].id // empty')
|
||||||
|
[[ -n "$RUN_ID" ]] || { echo "error: no successful runs found" >&2; exit 1; }
|
||||||
|
echo "Latest run: $RUN_ID"
|
||||||
|
fi
|
||||||
|
target="$CACHE_ROOT/run-$RUN_ID"
|
||||||
|
if [[ -d "$target/tests" ]]; then
|
||||||
|
echo "Reusing cached compliance data at $target"
|
||||||
|
DIR="$target"
|
||||||
|
else
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$tmpdir"' EXIT
|
||||||
|
echo "Downloading artifact from run $RUN_ID..."
|
||||||
|
gh run download "$RUN_ID" --repo ethereum/consensus-specs --name small.tar.gz --dir "$tmpdir"
|
||||||
|
[[ -f "$tmpdir/small.tar.gz" ]] || { echo "error: small.tar.gz not present in artifact" >&2; exit 1; }
|
||||||
|
extract_into "$tmpdir/small.tar.gz" "run-$RUN_ID"
|
||||||
|
rm -rf "$tmpdir"
|
||||||
|
trap - EXIT
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "error: no data source given." >&2
|
||||||
|
echo " Pass --dir / --tarball / --url / --run-id, or set GITHUB_TOKEN" >&2
|
||||||
|
echo " to auto-fetch the latest run. See --help for details." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d "$DIR/tests/$PRESET" ]]; then
|
||||||
|
echo "error: no $PRESET-preset compliance data at $DIR/tests/$PRESET" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- stage data into the ef_tests crate --------------------------------------
|
||||||
|
#
|
||||||
|
# `testing/ef_tests` resolves test paths from `env!("CARGO_MANIFEST_DIR")` at
|
||||||
|
# compile time, so the corpus must live under the crate. We copy only the
|
||||||
|
# fork_choice_compliance subtree to avoid clobbering any existing test corpus.
|
||||||
|
|
||||||
|
echo "Staging compliance data under ${TESTS_DST}/tests/${PRESET}/..."
|
||||||
|
mkdir -p "${TESTS_DST}/tests/${PRESET}"
|
||||||
|
for fork in "${ALL_FORKS[@]}"; do
|
||||||
|
src="${DIR}/tests/${PRESET}/${fork}/fork_choice_compliance"
|
||||||
|
if [[ ! -d "$src" ]]; then
|
||||||
|
echo " skip ${fork}: no fork_choice_compliance directory in source"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
dst="${TESTS_DST}/tests/${PRESET}/${fork}"
|
||||||
|
mkdir -p "$dst"
|
||||||
|
rm -rf "${dst}/fork_choice_compliance"
|
||||||
|
cp -R "$src" "${dst}/fork_choice_compliance"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---- run ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Resolve which suites to run.
|
||||||
|
SUITES=()
|
||||||
|
if [[ -n "$SUITE_FILTER" ]]; then
|
||||||
|
IFS=',' read -ra SUITES <<< "$SUITE_FILTER"
|
||||||
|
else
|
||||||
|
SUITES=("${ALL_SUITES[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
LOGS_DIR="${COMPLIANCE_FC_LOGS_DIR:-/tmp/compliance_fc_logs}"
|
||||||
|
rm -rf "$LOGS_DIR"
|
||||||
|
mkdir -p "$LOGS_DIR"
|
||||||
|
|
||||||
|
declare -a results=() # tab-separated rows: handler\tfork\ttotal\tpass\tfail\tskipped
|
||||||
|
|
||||||
|
run_one() {
|
||||||
|
local handler="$1" fork="$2"
|
||||||
|
local fn="fork_choice_compliance_${handler}_${fork}"
|
||||||
|
local log="${LOGS_DIR}/${handler}_${fork}.log"
|
||||||
|
echo "==> ${fn}"
|
||||||
|
|
||||||
|
RUST_MIN_STACK=8388608 \
|
||||||
|
cargo test --release --features "ef_tests,fake_crypto" \
|
||||||
|
-p ef_tests --test tests "$fn" \
|
||||||
|
${CARGO_EXTRA_ARGS[@]+"${CARGO_EXTRA_ARGS[@]}"} \
|
||||||
|
-- --nocapture --include-ignored \
|
||||||
|
> "$log" 2>&1 || true
|
||||||
|
|
||||||
|
# Parse the harness summary. Two cases:
|
||||||
|
# 1. "N tests, F failed, K skipped (known failure), B skipped (bls), P passed."
|
||||||
|
# 2. "Passed N tests in <path>" (when nothing failed at all).
|
||||||
|
local total=0 pass=0 fail=0 skip=0
|
||||||
|
local summary
|
||||||
|
summary=$(grep -E "^[0-9]+ tests, " "$log" | head -1)
|
||||||
|
if [[ -n "$summary" ]]; then
|
||||||
|
# shellcheck disable=SC2001
|
||||||
|
read -r total fail kfail bls pass <<< \
|
||||||
|
"$(sed -E 's/^([0-9]+) tests, ([0-9]+) failed, ([0-9]+) skipped \(known failure\), ([0-9]+) skipped \(bls\), ([0-9]+) passed.*/\1 \2 \3 \4 \5/' <<< "$summary")"
|
||||||
|
skip=$((kfail + bls))
|
||||||
|
else
|
||||||
|
summary=$(grep -E "^Passed [0-9]+ tests in " "$log" | head -1)
|
||||||
|
if [[ -n "$summary" ]]; then
|
||||||
|
total=$(awk '{print $2}' <<< "$summary")
|
||||||
|
pass="$total"
|
||||||
|
else
|
||||||
|
total=0; pass=0; fail=0; skip=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
results+=("$(printf '%s\t%s\t%d\t%d\t%d\t%d' "$handler" "$fork" "$total" "$pass" "$fail" "$skip")")
|
||||||
|
}
|
||||||
|
|
||||||
|
for handler in "${SUITES[@]}"; do
|
||||||
|
for fork in "${ALL_FORKS[@]}"; do
|
||||||
|
run_one "$handler" "$fork"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---- aggregate report --------------------------------------------------------
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Fork-choice compliance report ($PRESET) ==="
|
||||||
|
printf '%-32s %-8s %8s %8s %8s %8s\n' "Suite" "Fork" "Total" "Pass" "Fail" "Skip"
|
||||||
|
printf '%-32s %-8s %8s %8s %8s %8s\n' "-----" "----" "-----" "----" "----" "----"
|
||||||
|
tt=0; tp=0; tf=0; ts=0
|
||||||
|
for row in "${results[@]}"; do
|
||||||
|
IFS=$'\t' read -r handler fork total pass fail skip <<< "$row"
|
||||||
|
printf '%-32s %-8s %8d %8d %8d %8d\n' "$handler" "$fork" "$total" "$pass" "$fail" "$skip"
|
||||||
|
tt=$((tt+total)); tp=$((tp+pass)); tf=$((tf+fail)); ts=$((ts+skip))
|
||||||
|
done
|
||||||
|
printf '%-32s %-8s %8s %8s %8s %8s\n' "-----" "----" "-----" "----" "----" "----"
|
||||||
|
printf '%-32s %-8s %8d %8d %8d %8d\n' "TOTAL" "" "$tt" "$tp" "$tf" "$ts"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Per-suite logs (look here for failure details):"
|
||||||
|
echo " ${LOGS_DIR}/<suite>_<fork>.log"
|
||||||
|
|
||||||
|
# Exit non-zero if any case failed.
|
||||||
|
(( tf == 0 ))
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::decode::{ssz_decode_file, ssz_decode_file_with, ssz_decode_state, yaml_decode_file};
|
use crate::decode::{ssz_decode_file, ssz_decode_file_with, ssz_decode_state, yaml_decode_file};
|
||||||
use ::fork_choice::{PayloadVerificationStatus, ProposerHeadError};
|
use ::fork_choice::{
|
||||||
|
AttestationFromBlock, ForkChoiceStore, PayloadVerificationStatus, ProposerHeadError,
|
||||||
|
};
|
||||||
use beacon_chain::beacon_proposer_cache::compute_proposer_duties_from_head;
|
use beacon_chain::beacon_proposer_cache::compute_proposer_duties_from_head;
|
||||||
use beacon_chain::blob_verification::GossipBlobError;
|
use beacon_chain::blob_verification::GossipBlobError;
|
||||||
use beacon_chain::block_verification_types::LookupBlock;
|
use beacon_chain::block_verification_types::LookupBlock;
|
||||||
@@ -19,6 +21,7 @@ use beacon_chain::{
|
|||||||
custody_context::NodeCustodyType,
|
custody_context::NodeCustodyType,
|
||||||
test_utils::{BeaconChainHarness, EphemeralHarnessType},
|
test_utils::{BeaconChainHarness, EphemeralHarnessType},
|
||||||
};
|
};
|
||||||
|
use bls::AggregateSignature;
|
||||||
use execution_layer::{
|
use execution_layer::{
|
||||||
PayloadStatusV1, PayloadStatusV1Status, json_structures::JsonPayloadStatusV1Status,
|
PayloadStatusV1, PayloadStatusV1Status, json_structures::JsonPayloadStatusV1Status,
|
||||||
};
|
};
|
||||||
@@ -34,8 +37,8 @@ use types::{
|
|||||||
Attestation, AttestationRef, AttesterSlashing, AttesterSlashingRef, BeaconBlock, BeaconState,
|
Attestation, AttestationRef, AttesterSlashing, AttesterSlashingRef, BeaconBlock, BeaconState,
|
||||||
BlobSidecar, BlobsList, BlockImportSource, Checkpoint, DataColumnSidecar,
|
BlobSidecar, BlobsList, BlockImportSource, Checkpoint, DataColumnSidecar,
|
||||||
DataColumnSidecarList, DataColumnSubnetId, ExecutionBlockHash, Hash256, IndexedAttestation,
|
DataColumnSidecarList, DataColumnSubnetId, ExecutionBlockHash, Hash256, IndexedAttestation,
|
||||||
KzgProof, ProposerPreparationData, SignedBeaconBlock, SignedExecutionPayloadEnvelope, Slot,
|
IndexedPayloadAttestation, KzgProof, PayloadAttestationMessage, ProposerPreparationData,
|
||||||
Uint256,
|
SignedBeaconBlock, SignedExecutionPayloadEnvelope, Slot, Uint256,
|
||||||
};
|
};
|
||||||
|
|
||||||
// When set to true, cache any states fetched from the db.
|
// When set to true, cache any states fetched from the db.
|
||||||
@@ -78,6 +81,14 @@ pub struct Checks {
|
|||||||
get_proposer_head: Option<Hash256>,
|
get_proposer_head: Option<Hash256>,
|
||||||
should_override_forkchoice_update: Option<ShouldOverrideFcu>,
|
should_override_forkchoice_update: Option<ShouldOverrideFcu>,
|
||||||
head_payload_status: Option<u8>,
|
head_payload_status: Option<u8>,
|
||||||
|
viable_for_head_roots_and_weights: Option<Vec<RootAndWeight>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct RootAndWeight {
|
||||||
|
pub root: Hash256,
|
||||||
|
pub weight: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
@@ -108,6 +119,7 @@ pub enum Step<
|
|||||||
TAttesterSlashing,
|
TAttesterSlashing,
|
||||||
TPowBlock,
|
TPowBlock,
|
||||||
TExecutionPayload = String,
|
TExecutionPayload = String,
|
||||||
|
TPayloadAttestation = String,
|
||||||
> {
|
> {
|
||||||
Tick {
|
Tick {
|
||||||
tick: u64,
|
tick: u64,
|
||||||
@@ -123,9 +135,13 @@ pub enum Step<
|
|||||||
},
|
},
|
||||||
Attestation {
|
Attestation {
|
||||||
attestation: TAttestation,
|
attestation: TAttestation,
|
||||||
|
#[serde(default)]
|
||||||
|
valid: Option<bool>,
|
||||||
},
|
},
|
||||||
AttesterSlashing {
|
AttesterSlashing {
|
||||||
attester_slashing: TAttesterSlashing,
|
attester_slashing: TAttesterSlashing,
|
||||||
|
#[serde(default)]
|
||||||
|
valid: Option<bool>,
|
||||||
},
|
},
|
||||||
PowBlock {
|
PowBlock {
|
||||||
pow_block: TPowBlock,
|
pow_block: TPowBlock,
|
||||||
@@ -146,13 +162,17 @@ pub enum Step<
|
|||||||
execution_payload: TExecutionPayload,
|
execution_payload: TExecutionPayload,
|
||||||
valid: bool,
|
valid: bool,
|
||||||
},
|
},
|
||||||
|
PayloadAttestation {
|
||||||
|
payload_attestation: TPayloadAttestation,
|
||||||
|
#[serde(default)]
|
||||||
|
valid: Option<bool>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Default, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct Meta {
|
pub struct Meta {
|
||||||
#[serde(rename(deserialize = "description"))]
|
#[serde(rename(deserialize = "description"), default)]
|
||||||
_description: String,
|
_description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -170,6 +190,7 @@ pub struct ForkChoiceTest<E: EthSpec> {
|
|||||||
AttesterSlashing<E>,
|
AttesterSlashing<E>,
|
||||||
PowBlock,
|
PowBlock,
|
||||||
SignedExecutionPayloadEnvelope<E>,
|
SignedExecutionPayloadEnvelope<E>,
|
||||||
|
PayloadAttestationMessage,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
@@ -184,8 +205,10 @@ impl<E: EthSpec> LoadCase for ForkChoiceTest<E> {
|
|||||||
.expect("path must be valid OsStr")
|
.expect("path must be valid OsStr")
|
||||||
.to_string();
|
.to_string();
|
||||||
let spec = &testing_spec::<E>(fork_name);
|
let spec = &testing_spec::<E>(fork_name);
|
||||||
let steps: Vec<Step<String, String, Vec<String>, String, String, String>> =
|
#[allow(clippy::type_complexity)]
|
||||||
yaml_decode_file(&path.join("steps.yaml"))?;
|
let steps: Vec<
|
||||||
|
Step<String, String, Vec<String>, String, String, String, String, String>,
|
||||||
|
> = yaml_decode_file(&path.join("steps.yaml"))?;
|
||||||
// Resolve the object names in `steps.yaml` into actual decoded block/attestation objects.
|
// Resolve the object names in `steps.yaml` into actual decoded block/attestation objects.
|
||||||
let steps = steps
|
let steps = steps
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -217,31 +240,38 @@ impl<E: EthSpec> LoadCase for ForkChoiceTest<E> {
|
|||||||
valid,
|
valid,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Step::Attestation { attestation } => {
|
Step::Attestation { attestation, valid } => {
|
||||||
if fork_name.electra_enabled() {
|
if fork_name.electra_enabled() {
|
||||||
ssz_decode_file(&path.join(format!("{}.ssz_snappy", attestation))).map(
|
ssz_decode_file(&path.join(format!("{}.ssz_snappy", attestation))).map(
|
||||||
|attestation| Step::Attestation {
|
|attestation| Step::Attestation {
|
||||||
attestation: Attestation::Electra(attestation),
|
attestation: Attestation::Electra(attestation),
|
||||||
|
valid,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ssz_decode_file(&path.join(format!("{}.ssz_snappy", attestation))).map(
|
ssz_decode_file(&path.join(format!("{}.ssz_snappy", attestation))).map(
|
||||||
|attestation| Step::Attestation {
|
|attestation| Step::Attestation {
|
||||||
attestation: Attestation::Base(attestation),
|
attestation: Attestation::Base(attestation),
|
||||||
|
valid,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Step::AttesterSlashing { attester_slashing } => {
|
Step::AttesterSlashing {
|
||||||
|
attester_slashing,
|
||||||
|
valid,
|
||||||
|
} => {
|
||||||
if fork_name.electra_enabled() {
|
if fork_name.electra_enabled() {
|
||||||
ssz_decode_file(&path.join(format!("{}.ssz_snappy", attester_slashing)))
|
ssz_decode_file(&path.join(format!("{}.ssz_snappy", attester_slashing)))
|
||||||
.map(|attester_slashing| Step::AttesterSlashing {
|
.map(|attester_slashing| Step::AttesterSlashing {
|
||||||
attester_slashing: AttesterSlashing::Electra(attester_slashing),
|
attester_slashing: AttesterSlashing::Electra(attester_slashing),
|
||||||
|
valid,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
ssz_decode_file(&path.join(format!("{}.ssz_snappy", attester_slashing)))
|
ssz_decode_file(&path.join(format!("{}.ssz_snappy", attester_slashing)))
|
||||||
.map(|attester_slashing| Step::AttesterSlashing {
|
.map(|attester_slashing| Step::AttesterSlashing {
|
||||||
attester_slashing: AttesterSlashing::Base(attester_slashing),
|
attester_slashing: AttesterSlashing::Base(attester_slashing),
|
||||||
|
valid,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -301,6 +331,15 @@ impl<E: EthSpec> LoadCase for ForkChoiceTest<E> {
|
|||||||
valid,
|
valid,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Step::PayloadAttestation {
|
||||||
|
payload_attestation,
|
||||||
|
valid,
|
||||||
|
} => ssz_decode_file(&path.join(format!("{payload_attestation}.ssz_snappy"))).map(
|
||||||
|
|payload_attestation| Step::PayloadAttestation {
|
||||||
|
payload_attestation,
|
||||||
|
valid,
|
||||||
|
},
|
||||||
|
),
|
||||||
})
|
})
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
let anchor_state = ssz_decode_state(&path.join("anchor_state.ssz_snappy"), spec)?;
|
let anchor_state = ssz_decode_state(&path.join("anchor_state.ssz_snappy"), spec)?;
|
||||||
@@ -354,10 +393,27 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
|
|||||||
proofs.clone(),
|
proofs.clone(),
|
||||||
*valid,
|
*valid,
|
||||||
)?,
|
)?,
|
||||||
Step::Attestation { attestation } => tester.process_attestation(attestation)?,
|
Step::Attestation { attestation, valid } => {
|
||||||
Step::AttesterSlashing { attester_slashing } => {
|
let result = tester.process_attestation(attestation);
|
||||||
tester.process_attester_slashing(attester_slashing.to_ref())
|
// Compliance tests use `valid: false` to indicate the attestation is
|
||||||
|
// intentionally malformed and should be rejected. In that case, an error
|
||||||
|
// here is the expected outcome.
|
||||||
|
match valid {
|
||||||
|
Some(false) => {
|
||||||
|
if result.is_ok() {
|
||||||
|
return Err(Error::DidntFail(
|
||||||
|
"attestation marked valid=false should have been rejected"
|
||||||
|
.into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
_ => result?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Step::AttesterSlashing {
|
||||||
|
attester_slashing,
|
||||||
|
valid: _,
|
||||||
|
} => tester.process_attester_slashing(attester_slashing.to_ref()),
|
||||||
Step::PowBlock { pow_block } => tester.process_pow_block(pow_block),
|
Step::PowBlock { pow_block } => tester.process_pow_block(pow_block),
|
||||||
Step::OnPayloadInfo {
|
Step::OnPayloadInfo {
|
||||||
block_hash,
|
block_hash,
|
||||||
@@ -381,6 +437,7 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
|
|||||||
get_proposer_head,
|
get_proposer_head,
|
||||||
should_override_forkchoice_update: should_override_fcu,
|
should_override_forkchoice_update: should_override_fcu,
|
||||||
head_payload_status,
|
head_payload_status,
|
||||||
|
viable_for_head_roots_and_weights,
|
||||||
} = checks.as_ref();
|
} = checks.as_ref();
|
||||||
|
|
||||||
if let Some(expected_head) = head {
|
if let Some(expected_head) = head {
|
||||||
@@ -431,6 +488,10 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
|
|||||||
if let Some(expected_status) = head_payload_status {
|
if let Some(expected_status) = head_payload_status {
|
||||||
tester.check_head_payload_status(*expected_status)?;
|
tester.check_head_payload_status(*expected_status)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(expected) = viable_for_head_roots_and_weights {
|
||||||
|
tester.check_viable_for_head_roots_and_weights(expected)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Step::MaybeValidBlockAndColumns {
|
Step::MaybeValidBlockAndColumns {
|
||||||
@@ -446,6 +507,24 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
|
|||||||
} => {
|
} => {
|
||||||
tester.process_execution_payload(execution_payload, *valid)?;
|
tester.process_execution_payload(execution_payload, *valid)?;
|
||||||
}
|
}
|
||||||
|
Step::PayloadAttestation {
|
||||||
|
payload_attestation,
|
||||||
|
valid,
|
||||||
|
} => {
|
||||||
|
let result = tester.process_payload_attestation(payload_attestation);
|
||||||
|
match valid {
|
||||||
|
Some(false) => {
|
||||||
|
if result.is_ok() {
|
||||||
|
return Err(Error::DidntFail(
|
||||||
|
"payload attestation marked valid=false should have been \
|
||||||
|
rejected"
|
||||||
|
.into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => result?,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,14 +680,16 @@ impl<E: EthSpec> Tester<E> {
|
|||||||
|| Ok(()),
|
|| Ok(()),
|
||||||
))?
|
))?
|
||||||
.map(|avail: AvailabilityProcessingStatus| avail.try_into());
|
.map(|avail: AvailabilityProcessingStatus| avail.try_into());
|
||||||
let success = data_column_success && result.as_ref().is_ok_and(|inner| inner.is_ok());
|
let is_duplicate = matches!(
|
||||||
|
&result,
|
||||||
|
Err(beacon_chain::BlockError::DuplicateFullyImported(_))
|
||||||
|
);
|
||||||
|
let success = data_column_success
|
||||||
|
&& (result.as_ref().is_ok_and(|inner| inner.is_ok()) || is_duplicate);
|
||||||
if success != valid {
|
if success != valid {
|
||||||
return Err(Error::DidntFail(format!(
|
return Err(Error::DidntFail(format!(
|
||||||
"block with root {} was valid={} whilst test expects valid={}. result: {:?}",
|
"block with root {} was valid={} whilst test expects valid={}. result: {:?}",
|
||||||
block_root,
|
block_root, success, valid, result
|
||||||
result.is_ok(),
|
|
||||||
valid,
|
|
||||||
result
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -700,14 +781,19 @@ impl<E: EthSpec> Tester<E> {
|
|||||||
|| Ok(()),
|
|| Ok(()),
|
||||||
))?
|
))?
|
||||||
.map(|avail: AvailabilityProcessingStatus| avail.try_into());
|
.map(|avail: AvailabilityProcessingStatus| avail.try_into());
|
||||||
let success = blob_success && result.as_ref().is_ok_and(|inner| inner.is_ok());
|
// Spec `on_block` is idempotent: re-importing an already-known block is a no-op
|
||||||
|
// success. Lighthouse surfaces this as `BlockError::DuplicateFullyImported`; the
|
||||||
|
// compliance suite re-feeds blocks repeatedly, so treat duplicates as success.
|
||||||
|
let is_duplicate = matches!(
|
||||||
|
&result,
|
||||||
|
Err(beacon_chain::BlockError::DuplicateFullyImported(_))
|
||||||
|
);
|
||||||
|
let success =
|
||||||
|
blob_success && (result.as_ref().is_ok_and(|inner| inner.is_ok()) || is_duplicate);
|
||||||
if success != valid {
|
if success != valid {
|
||||||
return Err(Error::DidntFail(format!(
|
return Err(Error::DidntFail(format!(
|
||||||
"block with root {} was valid={} whilst test expects valid={}. result: {:?}",
|
"block with root {} was valid={} whilst test expects valid={}. result: {:?}",
|
||||||
block_root,
|
block_root, success, valid, result
|
||||||
result.is_ok(),
|
|
||||||
valid,
|
|
||||||
result
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -815,6 +901,35 @@ impl<E: EthSpec> Tester<E> {
|
|||||||
.map_err(|e| Error::InternalError(format!("attestation import failed with {:?}", e)))
|
.map_err(|e| Error::InternalError(format!("attestation import failed with {:?}", e)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn process_payload_attestation(
|
||||||
|
&self,
|
||||||
|
message: &PayloadAttestationMessage,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let head = self.harness.chain.canonical_head.cached_head();
|
||||||
|
let head_state = &head.snapshot.beacon_state;
|
||||||
|
let slot = message.data.slot;
|
||||||
|
let ptc = head_state
|
||||||
|
.get_ptc(slot, &self.spec)
|
||||||
|
.map_err(|e| Error::InternalError(format!("get_ptc failed with {:?}", e)))?;
|
||||||
|
|
||||||
|
let indexed = IndexedPayloadAttestation {
|
||||||
|
attesting_indices: vec![message.validator_index].try_into().map_err(|_| {
|
||||||
|
Error::InternalError("payload attestation indexing failed: too many indices".into())
|
||||||
|
})?,
|
||||||
|
data: message.data.clone(),
|
||||||
|
signature: AggregateSignature::from(&message.signature),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.harness
|
||||||
|
.chain
|
||||||
|
.canonical_head
|
||||||
|
.fork_choice_write_lock()
|
||||||
|
.on_payload_attestation(slot, &indexed, AttestationFromBlock::False, &ptc.0)
|
||||||
|
.map_err(|e| {
|
||||||
|
Error::InternalError(format!("payload attestation import failed with {:?}", e))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn process_attester_slashing(&self, attester_slashing: AttesterSlashingRef<E>) {
|
pub fn process_attester_slashing(&self, attester_slashing: AttesterSlashingRef<E>) {
|
||||||
self.harness
|
self.harness
|
||||||
.chain
|
.chain
|
||||||
@@ -1094,6 +1209,45 @@ impl<E: EthSpec> Tester<E> {
|
|||||||
check_equal("head_payload_status", actual, expected_status)
|
check_equal("head_payload_status", actual, expected_status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_viable_for_head_roots_and_weights(
|
||||||
|
&self,
|
||||||
|
expected: &[RootAndWeight],
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Apply pending vote deltas so weights reflect the latest store state.
|
||||||
|
let _ = self.find_head()?;
|
||||||
|
|
||||||
|
let fork_choice = self.harness.chain.canonical_head.fork_choice_read_lock();
|
||||||
|
let justified = fork_choice.justified_checkpoint();
|
||||||
|
let finalized = fork_choice.finalized_checkpoint();
|
||||||
|
let current_slot = fork_choice.fc_store().get_current_slot();
|
||||||
|
let actual = fork_choice
|
||||||
|
.proto_array()
|
||||||
|
.filtered_block_tree_leaves_and_weights::<E>(
|
||||||
|
&justified.root,
|
||||||
|
current_slot,
|
||||||
|
justified,
|
||||||
|
finalized,
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
Error::InternalError(format!(
|
||||||
|
"filtered_block_tree_leaves_and_weights failed: {e}"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
drop(fork_choice);
|
||||||
|
|
||||||
|
let mut actual_sorted = actual;
|
||||||
|
actual_sorted.sort();
|
||||||
|
let mut expected_sorted: Vec<(Hash256, u64)> =
|
||||||
|
expected.iter().map(|x| (x.root, x.weight)).collect();
|
||||||
|
expected_sorted.sort();
|
||||||
|
|
||||||
|
check_equal(
|
||||||
|
"viable_for_head_roots_and_weights",
|
||||||
|
actual_sorted,
|
||||||
|
expected_sorted,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_should_override_fcu(
|
pub fn check_should_override_fcu(
|
||||||
&self,
|
&self,
|
||||||
expected_should_override_fcu: ShouldOverrideFcu,
|
expected_should_override_fcu: ShouldOverrideFcu,
|
||||||
|
|||||||
@@ -746,6 +746,73 @@ impl<E: EthSpec + TypeName> Handler for ForkChoiceHandler<E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ForkChoiceComplianceHandler<E> {
|
||||||
|
handler_name: String,
|
||||||
|
only_fork: Option<ForkName>,
|
||||||
|
_phantom: PhantomData<E>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: EthSpec> ForkChoiceComplianceHandler<E> {
|
||||||
|
pub fn new(handler_name: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
handler_name: handler_name.into(),
|
||||||
|
only_fork: None,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn only_fork(mut self, fork: ForkName) -> Self {
|
||||||
|
self.only_fork = Some(fork);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: EthSpec + TypeName> Handler for ForkChoiceComplianceHandler<E> {
|
||||||
|
type Case = cases::ForkChoiceTest<E>;
|
||||||
|
|
||||||
|
fn config_name() -> &'static str {
|
||||||
|
E::name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runner_name() -> &'static str {
|
||||||
|
"fork_choice_compliance"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handler_name(&self) -> String {
|
||||||
|
self.handler_name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_rayon() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
|
||||||
|
// Compliance tests are only generated for fulu and gloas (post-Electra).
|
||||||
|
if !fork_name.fulu_enabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Gloas anchor states currently fail to initialise the test harness with
|
||||||
|
// "Head block not found in store" after the recent payload-envelope DB
|
||||||
|
// changes (see https://github.com/sigp/lighthouse/pull/8886). Skip gloas
|
||||||
|
// here until that path is fixed; fulu compliance still runs.
|
||||||
|
if fork_name.gloas_enabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Some(only) = self.only_fork
|
||||||
|
&& only != fork_name
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Compliance generators emit bogus BLS signatures (`bls_setting: 2`); SSZ-decoding
|
||||||
|
// them with real BLS yields BLST_BAD_ENCODING. They must run with `fake_crypto`.
|
||||||
|
cfg!(feature = "fake_crypto")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disabled_forks(&self) -> Vec<ForkName> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Educe)]
|
#[derive(Educe)]
|
||||||
#[educe(Default)]
|
#[educe(Default)]
|
||||||
pub struct OptimisticSyncHandler<E>(PhantomData<E>);
|
pub struct OptimisticSyncHandler<E>(PhantomData<E>);
|
||||||
|
|||||||
@@ -1079,6 +1079,108 @@ fn fork_choice_get_parent_payload_status() {
|
|||||||
ForkChoiceHandler::<MainnetEthSpec>::new("get_parent_payload_status").run();
|
ForkChoiceHandler::<MainnetEthSpec>::new("get_parent_payload_status").run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compliance tests surface real consensus deltas (proposer-boost timing, viable-tree
|
||||||
|
// weights) that we want to be able to run on demand without blocking CI. They are gated
|
||||||
|
// behind `#[ignore]` and run via `scripts/compliance-fc-report.sh` (which passes
|
||||||
|
// `--include-ignored` to cargo test). To run them directly:
|
||||||
|
// cargo test --release --features "ef_tests,fake_crypto" -p ef_tests --test tests \
|
||||||
|
// fork_choice_compliance_ -- --include-ignored
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn fork_choice_compliance_attester_slashing_test_fulu() {
|
||||||
|
ForkChoiceComplianceHandler::<MinimalEthSpec>::new("attester_slashing_test")
|
||||||
|
.only_fork(ForkName::Fulu)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn fork_choice_compliance_attester_slashing_test_gloas() {
|
||||||
|
ForkChoiceComplianceHandler::<MinimalEthSpec>::new("attester_slashing_test")
|
||||||
|
.only_fork(ForkName::Gloas)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn fork_choice_compliance_block_cover_test_fulu() {
|
||||||
|
ForkChoiceComplianceHandler::<MinimalEthSpec>::new("block_cover_test")
|
||||||
|
.only_fork(ForkName::Fulu)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn fork_choice_compliance_block_cover_test_gloas() {
|
||||||
|
ForkChoiceComplianceHandler::<MinimalEthSpec>::new("block_cover_test")
|
||||||
|
.only_fork(ForkName::Gloas)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn fork_choice_compliance_block_tree_test_fulu() {
|
||||||
|
ForkChoiceComplianceHandler::<MinimalEthSpec>::new("block_tree_test")
|
||||||
|
.only_fork(ForkName::Fulu)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn fork_choice_compliance_block_tree_test_gloas() {
|
||||||
|
ForkChoiceComplianceHandler::<MinimalEthSpec>::new("block_tree_test")
|
||||||
|
.only_fork(ForkName::Gloas)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn fork_choice_compliance_block_weight_test_fulu() {
|
||||||
|
ForkChoiceComplianceHandler::<MinimalEthSpec>::new("block_weight_test")
|
||||||
|
.only_fork(ForkName::Fulu)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn fork_choice_compliance_block_weight_test_gloas() {
|
||||||
|
ForkChoiceComplianceHandler::<MinimalEthSpec>::new("block_weight_test")
|
||||||
|
.only_fork(ForkName::Gloas)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn fork_choice_compliance_invalid_message_test_fulu() {
|
||||||
|
ForkChoiceComplianceHandler::<MinimalEthSpec>::new("invalid_message_test")
|
||||||
|
.only_fork(ForkName::Fulu)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn fork_choice_compliance_invalid_message_test_gloas() {
|
||||||
|
ForkChoiceComplianceHandler::<MinimalEthSpec>::new("invalid_message_test")
|
||||||
|
.only_fork(ForkName::Gloas)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn fork_choice_compliance_shuffling_test_fulu() {
|
||||||
|
ForkChoiceComplianceHandler::<MinimalEthSpec>::new("shuffling_test")
|
||||||
|
.only_fork(ForkName::Fulu)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn fork_choice_compliance_shuffling_test_gloas() {
|
||||||
|
ForkChoiceComplianceHandler::<MinimalEthSpec>::new("shuffling_test")
|
||||||
|
.only_fork(ForkName::Gloas)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn optimistic_sync() {
|
fn optimistic_sync() {
|
||||||
OptimisticSyncHandler::<MinimalEthSpec>::default().run();
|
OptimisticSyncHandler::<MinimalEthSpec>::default().run();
|
||||||
|
|||||||
Reference in New Issue
Block a user