diff --git a/.github/workflows/hive.yaml b/.github/workflows/hive.yaml new file mode 100644 index 0000000..1c9da11 --- /dev/null +++ b/.github/workflows/hive.yaml @@ -0,0 +1,51 @@ +name: Hive + +# Runs on manual dispatch, and on pull requests only when they carry the +# `run-hive` label (added on label, and re-run on subsequent pushes) so the slow +# suite doesn't run on every PR push. +on: + workflow_dispatch: + pull_request: + types: [labeled, synchronize] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + hive: + name: Run hive tests + runs-on: ubuntu-latest + # Skip on PRs unless the `run-hive` label is present; always run on dispatch. + if: >- + github.event_name != 'pull_request' || + contains(github.event.pull_request.labels.*.name, 'run-hive') + # hive itself imposes no timeout; cap the job so a hang can't run forever. + timeout-minutes: 180 + env: + # `docker-local` -> `cross` bind-mounts this into the build container. + LEAN_REPO_ROOT: ${{ github.workspace }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + submodules: recursive + + # Needed to build the hive binary (`go build .`). + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: stable + cache: false + + # Needed by `docker-local` to cross-build the x86_64 client binary. + - name: Install cross + run: cargo install cross --git https://github.com/cross-rs/cross --rev 7b24b6e9f6834a3ac31d3dad1e2ff24c1b66f7cf + + # hive launches client containers; restart docker to settle iptables + # rules on the runner (same workaround the zeam hive workflow uses). + - name: Restart docker + run: sudo systemctl restart docker + + - name: Run hive tests + run: make test-hive diff --git a/.gitignore b/.gitignore index 0ed8991..96635e6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ lean_client/target/* lean_client/tests/mainnet lean_client/bin lean_client/spec/ +lean_client/hive/ target/* *.local* diff --git a/Makefile b/Makefile index d000b1c..908ff7c 100644 --- a/Makefile +++ b/Makefile @@ -32,3 +32,7 @@ docker-local: .PHONY: release release: $(MAKE) -C lean_client release + +.PHONY: test-hive +test-hive: + $(MAKE) -C lean_client test-hive diff --git a/README.md b/README.md index 5546c2c..688a852 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,27 @@ leanEthereum Consensus Client written in Rust using Grandine's libraries. ``` After a minute all the nodes should be synced up and see each other + +## Running Hive tests + +Runs the [Hive](https://github.com/ethereum/hive) lean simulator against our +`grandine_lean` client. From the repo root: + +```bash +make test-hive +``` + +Requires a **Go toolchain**, **Docker** (running, usable without `sudo`), and +**Rust + [`cross`](https://github.com/cross-rs/cross)**. + +Pin a hive version (defaults to latest `master`): + +```bash +make test-hive HIVE_VERSION= +``` + +Remove the hive clone and other build artifacts: + +```bash +make clean +``` diff --git a/lean_client/Makefile b/lean_client/Makefile index b363be3..3adbfd6 100644 --- a/lean_client/Makefile +++ b/lean_client/Makefile @@ -81,18 +81,23 @@ build: clean: cargo clean rm -rf ./bin + rm -rf ./hive + +CLIENT_SOURCES := $(shell find . \( -name '*.rs' -o -name 'Cargo.toml' \) \ + -not -path './target/*' -not -path './hive/*' -not -path './spec/*' \ + -not -path './bin/*') Cargo.lock .PHONY: x86_64-unknown-linux-gnu x86_64-unknown-linux-gnu: ./target/x86_64-unknown-linux-gnu/release/lean_client -./target/x86_64-unknown-linux-gnu/release/lean_client: +./target/x86_64-unknown-linux-gnu/release/lean_client: $(CLIENT_SOURCES) CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C target-cpu=x86-64-v3" \ LEAN_REPO_ROOT=$(LEAN_REPO_ROOT) cross build --bin lean_client --target x86_64-unknown-linux-gnu --profile release .PHONY: aarch64-unknown-linux-gnu aarch64-unknown-linux-gnu: ./target/aarch64-unknown-linux-gnu/release/lean_client -./target/aarch64-unknown-linux-gnu/release/lean_client: +./target/aarch64-unknown-linux-gnu/release/lean_client: $(CLIENT_SOURCES) LEAN_REPO_ROOT=$(LEAN_REPO_ROOT) cross build --bin lean_client --target aarch64-unknown-linux-gnu --profile release .PHONY: release @@ -126,6 +131,74 @@ docker-local: ./target/x86_64-unknown-linux-gnu/release/lean_client $(DOCKER_TAGS) \ . +### Hive +# ethereum/hive ref to build. Unset (default) => latest master, resolved at +# clone time. Override with `make test-hive HIVE_VERSION=` to pin. +HIVE_VERSION ?= +HIVE_REF := $(or $(HIVE_VERSION),master) + +.PHONY: test-hive +test-hive: hive/hive docker-local + @cd hive && \ + ./hive --sim lean \ + --client-file simulators/lean/clients/devnet5.yaml \ + --client grandine_lean_devnet5 \ + --docker.output \ + --docker.nocache="hive/clients/grandine_lean_devnet5" \ + --results-root ./workspace/logs; \ + hive_status=$$?; \ + logs=./workspace/logs; \ + suites=$$(ls $$logs/*.json 2>/dev/null | grep -v '/hive\.json$$' || true); \ + if [ -z "$$suites" ]; then \ + echo "ERROR: hive produced no suite results - treating as an infrastructure/script failure."; \ + exit $$hive_status; \ + fi; \ + if ! command -v jq >/dev/null 2>&1; then \ + echo "jq not found; skipping summary. Raw results under $$logs"; \ + exit 0; \ + fi; \ + summary=$$( \ + echo "==================== Hive test summary ===================="; \ + total=0; failed_total=0; \ + for f in $$suites; do \ + name=$$(jq -r '.name' "$$f"); \ + n=$$(jq '.testCases | length' "$$f"); \ + nf=$$(jq '[.testCases[] | select(.summaryResult.pass | not)] | length' "$$f"); \ + total=$$((total + n)); failed_total=$$((failed_total + nf)); \ + if [ "$$nf" -eq 0 ]; then \ + printf '%s: %d/%d passed\n' "$$name" "$$n" "$$n"; \ + else \ + printf '%s: %d/%d passed (%d failed)\n' "$$name" "$$((n - nf))" "$$n" "$$nf"; \ + jq -r '.testCases[] | select(.summaryResult.pass | not) | " FAIL: \(.name)"' "$$f"; \ + fi; \ + done; \ + echo "-----------------------------------------------------------"; \ + printf 'Total: %d/%d passed, %d failed\n' "$$((total - failed_total))" "$$total" "$$failed_total"; \ + echo "Full failure details: lean_client/hive/workspace/logs/details/"; \ + echo "===========================================================" \ + ); \ + echo; \ + printf '%s\n' "$$summary"; \ + if [ -n "$$GITHUB_STEP_SUMMARY" ]; then \ + { echo '```'; printf '%s\n' "$$summary"; echo '```'; } >> "$$GITHUB_STEP_SUMMARY"; \ + fi + +hive/hive: hive/.hive-ref-$(HIVE_REF) + @echo "==> Building hive binary (go build)..." + @cd hive && go build . + @echo "==> Built hive binary at hive/hive ($$(cd hive && git rev-parse --short HEAD))" + +hive/.hive-ref-$(HIVE_REF): + @echo "==> Fetching ethereum/hive at ref '$(HIVE_REF)'..." + @rm -rf hive + @mkdir -p hive + @cd hive && git init -q && \ + git remote add origin https://github.com/ethereum/hive.git && \ + git fetch --depth 1 -q origin $(HIVE_REF) && \ + git switch -q --detach FETCH_HEAD + @echo "==> Checked out hive at $$(cd hive && git rev-parse --short HEAD)" + @touch $@ + ### Shadow-simulator support # `rust/patch/quinn-udp` is a vendored fallback-only build of quinn-udp so QUIC # runs under the Shadow simulator (which does not emulate sendmsg cmsg / GRO /