diff --git a/.cargo/config.toml b/.cargo/config.toml index 934b5126..19c7f8f7 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,3 +3,16 @@ rustflags = [ "-Ctarget-cpu=x86-64-v3", "-Ctarget-feature=+avx2,+sse2,+ssse3,+sse4.1,+sse4.2,+bmi1,+lzcnt,+pclmulqdq", ] + +# Supply-chain cooldown for dependency resolution (unstable min-publish-age, +# tracking issue rust-lang/cargo#17009): crate versions published less than +# 14 days ago are excluded when the resolver runs on a nightly cargo. +# Stable cargo ignores these tables silently, so builds from the committed +# Cargo.lock are unaffected; run `make update` to resolve under the policy. +# When the feature stabilizes, drop the [unstable] table and the nightly +# resolver pin in the Makefile: the policy then binds all resolution. +[unstable] +min-publish-age = true + +[registry] +global-min-publish-age = "14 days" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a22d9309..b3229101 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,10 +44,10 @@ jobs: run: cargo fmt --all -- --check - name: Cargo check - run: cargo check --workspace --all-targets + run: cargo check --locked --workspace --all-targets - name: Clippy - run: cargo clippy --workspace --all-targets -- -D warnings + run: cargo clippy --locked --workspace --all-targets -- -D warnings # tooling/event-monitor declares its own [workspace] table, so every step # above stops at the root workspace members and never reaches it. Its @@ -80,6 +80,44 @@ jobs: # harness end-to-end and its JSON output contract in a few seconds. - name: Benchmark smoke (mock crypto) run: | - cargo run --profile release-fast --bin ethlambda -- benchmark synthetic --mock-crypto \ + cargo run --locked --profile release-fast --bin ethlambda -- benchmark synthetic --mock-crypto \ --num-validators 4 --warmup-slots 4 --iterations 3 --format json \ | jq -e '.schema_version == 1 and (.samples | length == 3)' + + # Stable cargo ignores the publish-age cooldown in .cargo/config.toml, so the + # lockfile can pin too-young crates. Fail the build when either lockfile does. + # Infrastructure failures (toolchain download, a resolution that fails for + # reasons unrelated to age, e.g. a yanked upstream crate) only warn: they are + # outside the PR's control and would block every PR. + cooldown: + name: Dependency cooldown + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Check lockfiles against the publish-age cooldown + run: | + if ! rustup toolchain install nightly-2026-06-21 --profile minimal; then + echo "::warning title=Publish-age cooldown check skipped::toolchain install failed" + exit 0 + fi + status=0 + for manifest in Cargo.toml tooling/event-monitor/Cargo.toml; do + if ! cargo +nightly-2026-06-21 update --dry-run -Z min-publish-age --manifest-path "$manifest" > cooldown.txt 2>&1; then + # Drop the index/git refresh chatter so the excerpt is the actual error; %0A = newline in annotations + msg=$(grep -v '^ *Updating ' cooldown.txt | head -20 | sed ':a;N;$!ba;s/\n/%0A/g') + echo "::warning title=Publish-age cooldown probe failed for $manifest::$msg" + continue + fi + # A cooldown-driven downgrade is annotated with the too-young version's + # publish date; downgrades for other reasons (MSRV, a tightened + # requirement) carry no such note and are not this check's business. + hits=$(grep -E '^ *Downgrading .*published' cooldown.txt || true) + if [ -n "$hits" ]; then + count=$(echo "$hits" | wc -l | tr -d ' ') + msg=$(echo "$hits" | head -20 | sed ':a;N;$!ba;s/\n/%0A/g') + echo "::error title=$manifest pins $count crate(s) younger than the publish-age cooldown::$msg" + status=1 + fi + done + exit $status diff --git a/CLAUDE.md b/CLAUDE.md index 7ce0bb6c..e8f22ae9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -90,6 +90,8 @@ make test # All tests + forkchoice spec tests ### Common Operations ```bash rm -rf leanSpec && make leanSpec/fixtures # Download latest released test fixtures +make update UPDATE_ARGS="-p " # Bump deps under the 14-day publish-age cooldown (nightly resolver) +make cooldown-check # Fail if a lockfile pins crates younger than the cooldown (same as CI) make docker-build # Build Docker image (DOCKER_TAG=local) make run-devnet # Run local devnet with lean-quickstart ``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e9afb826..3ca8c736 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,6 +96,12 @@ All commits must have a verified signature. - **Comments:** Explain *why*, not *what*. Code should be self-explanatory. - **Error handling:** Use `Result` and `thiserror`. Avoid `.unwrap()` outside tests. - **Dependencies:** Adding a new crate requires justification in the PR description. + Resolve version bumps with `make update` (optionally `UPDATE_ARGS="-p "`): it runs + the resolver under a 14-day publish-age cooldown (`.cargo/config.toml`) that excludes + freshly published crate versions as a supply-chain precaution. A plain `cargo update` or + `cargo add` on stable bypasses the cooldown; `make lint`/`make test` and CI build + `--locked` so an unresolved manifest change fails loudly instead of silently re-resolving. + Git dependencies have no publish age and are not covered. ### Review Process diff --git a/Dockerfile b/Dockerfile index df228ea1..16e62239 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,7 +38,7 @@ ENV NO_DEFAULT_FEATURES=$NO_DEFAULT_FEATURES ARG LOCKED="--locked" ENV LOCKED=$LOCKED -RUN cargo chef cook --profile $BUILD_PROFILE $NO_DEFAULT_FEATURES --features "$FEATURES" --recipe-path recipe.json +RUN cargo chef cook --profile $BUILD_PROFILE $NO_DEFAULT_FEATURES --features "$FEATURES" $LOCKED --recipe-path recipe.json # Build application # Include .git so vergen-git2 can extract version info (branch, commit SHA) diff --git a/Makefile b/Makefile index ee6c28dc..a7836287 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help fmt lint bench docker-build shadow-build shadow-docker-build run-devnet test docs docs-deps docs-serve +.PHONY: help fmt lint bench update cooldown-check docker-build shadow-build shadow-docker-build run-devnet test docs docs-deps docs-serve help: ## ๐Ÿ“š Show help for each of the Makefile recipes @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @@ -7,12 +7,42 @@ fmt: ## ๐ŸŽจ Format all code using rustfmt cargo fmt --all lint: ## ๐Ÿ” Run clippy on all workspace crates - cargo clippy --workspace --all-targets -- -D warnings + cargo clippy --locked --workspace --all-targets -- -D warnings test: leanSpec/fixtures ## ๐Ÿงช Run all tests # release-fast: release-grade opt-level to avoid stack overflows during # signature verification/aggregation, without paying for LTO on every rebuild - cargo test --workspace --profile release-fast + cargo test --locked --workspace --profile release-fast + +# Used ONLY to resolve dependency updates: min-publish-age (.cargo/config.toml) +# is nightly-only, everything else runs on the stable toolchain pinned in +# rust-toolchain.toml. +RESOLVER_TOOLCHAIN := nightly-2026-06-21 + +# Versions published less than 14 days ago are excluded from resolution. +# Resolution done on stable (`cargo add`, plain `cargo update`) is NOT covered; +# this target is the intended path for routine updates. Git dependencies have +# no publish age and are refreshed WITHOUT any cooldown: review their lockfile +# rev changes manually. +update: ## ๐Ÿ“ฆ Update dependencies under the publish-age cooldown (UPDATE_ARGS="-p foo") + rustup toolchain install $(RESOLVER_TOOLCHAIN) --profile minimal > /dev/null && \ + cargo +$(RESOLVER_TOOLCHAIN) update -Z min-publish-age $(UPDATE_ARGS) + +# Stable cargo ignores the cooldown, so a lockfile can pin too-young crates; +# same check as the CI `cooldown` job, without touching the files. A cooldown +# downgrade is annotated with the too-young version's publish date; downgrades +# for other reasons carry no such note and are not flagged. +cooldown-check: ## ๐Ÿ”Ž Fail if a lockfile pins crates younger than the publish-age cooldown + @rustup toolchain install $(RESOLVER_TOOLCHAIN) --profile minimal > /dev/null && \ + status=0; \ + for manifest in Cargo.toml tooling/event-monitor/Cargo.toml; do \ + if ! out=$$(cargo +$(RESOLVER_TOOLCHAIN) update --dry-run -Z min-publish-age --manifest-path $$manifest 2>&1); then \ + echo "WARNING: publish-age cooldown probe failed for $$manifest:"; echo "$$out" | grep -v "^ *Updating " | head -20; continue; \ + fi; \ + hits=$$(echo "$$out" | grep -E "^ *Downgrading .*published" || true); \ + if [ -n "$$hits" ]; then echo "ERROR: $$manifest pins crates younger than the publish-age cooldown:"; echo "$$hits"; status=1; fi; \ + done; \ + exit $$status BENCH_ARGS ?= synthetic --mock-crypto