diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..f2b9526 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,81 @@ +--- +# Lint, build and test on the fleet's `rust` runner (architecture/gitea-runners.md). +# +# Replaces origin's .github/workflows/ci.yml, which targeted GitHub-hosted +# runners that do not exist here. Gitea Actions also reads .github/workflows, +# so origin's files are deleted rather than left inert; on every origin merge +# `git rm -r .github/workflows` is the standing resolution (quantus/miner#1). +# +# Deliberately not here: origin's CPU benchmark job (meaningless on a shared +# 4-CPU runner; the real gate is the GPU harness, quantus/miner#2) and taplo +# (not on the runner image; add it to runner-rust per gitea-runners.md ยง5 +# rather than `cargo install` on every run). +name: ci + +on: + pull_request: + push: + branches: [main, origin-main] + +concurrency: + group: ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + CARGO_INCREMENTAL: "0" + CARGO_TERM_COLOR: always + +# rust-toolchain pins 1.93.0. rustup on the runner auto-installs the toolchain +# on first use but NOT the components the file lists (observed: "cargo-fmt is +# not installed for the toolchain"), so each job that needs one adds it +# explicitly; the call is a no-op once present. + +jobs: + fmt: + runs-on: rust + steps: + - uses: actions/checkout@v4 + - name: toolchain + run: rustup component add rustfmt + - name: rustfmt + run: cargo fmt --all -- --check + + clippy: + runs-on: rust + needs: fmt + steps: + - uses: actions/checkout@v4 + - name: toolchain + run: rustup component add clippy + - name: clippy (all targets, all features, warnings denied) + # Same invocation as clippy.sh so local and CI agree. + run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings + + test: + runs-on: rust + needs: fmt + steps: + - uses: actions/checkout@v4 + - name: build + run: cargo build --locked --workspace + - name: test + # GPU tests skip themselves when no adapter is present; the runner has none. + run: cargo test --locked --workspace + - name: version carries the commit + # build.rs embeds the SHA; a binary that says "unknown" cannot be + # matched to a deploy (quantus/miner#8). + run: | + set -euo pipefail + v=$(./target/debug/quantus-miner --version) + echo "$v" + case "$v" in + *unknown*) echo "build SHA not embedded" >&2; exit 1 ;; + esac + + doc: + runs-on: rust + needs: fmt + steps: + - uses: actions/checkout@v4 + - name: doc + run: cargo doc --locked --workspace --no-deps --all-features diff --git a/.github/actions/disk/action.yml b/.github/actions/disk/action.yml deleted file mode 100644 index 580153c..0000000 --- a/.github/actions/disk/action.yml +++ /dev/null @@ -1,15 +0,0 @@ ---- -name: free disk space -description: when rust compiling, free up some disk space - -runs: - using: composite - steps: - - name: free disk space - uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # 1.3.1 - with: - android: true - dotnet: false - haskell: false - large-packages: false - swap-storage: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index a36a4dd..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,96 +0,0 @@ ---- -name: CI - -on: - pull_request: - paths-ignore: - - "docs/**" - - "*.md" - - "LICENSE" - push: - branches: - - main - paths-ignore: - - "docs/**" - - "*.md" - - "LICENSE" - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -env: - CARGO_INCREMENTAL: 0 - CARGO_TERM_COLOR: always - -jobs: - fast-checks: - name: ๐Ÿ Fast Checks (Format) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: setup rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - components: rustfmt - - name: install taplo - run: cargo install taplo-cli --locked - - name: Run format checks - run: | - taplo format --check --config taplo.toml - cargo fmt --all -- --check - timeout-minutes: 5 - - build-and-test: - name: ๐Ÿ› ๏ธ Build & Test - needs: fast-checks - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: setup rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - - name: compile (gpu) - run: | - cargo build --locked --workspace - timeout-minutes: 90 - - name: test (gpu) - run: | - cargo test --locked --workspace - timeout-minutes: 15 - - analysis: - name: ๐Ÿค– Analysis (Clippy & Doc) - needs: fast-checks - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: setup rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - components: clippy - - name: clippy (all features) - run: cargo clippy --locked --workspace --all-features - timeout-minutes: 30 - - name: doc - run: cargo doc --locked --workspace --no-deps --all-features - timeout-minutes: 15 - - benchmark: - name: ๐Ÿƒ Benchmark - needs: fast-checks - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: setup rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - - name: build benchmark binary - run: cargo build -p miner-cli --release - timeout-minutes: 60 - - name: upload linux release binary - uses: actions/upload-artifact@v4 - with: - name: quantus-miner-linux-x86_64 - path: target/release/quantus-miner - - name: run cpu benchmark - run: ./target/release/quantus-miner benchmark --cpu-workers 2 --duration 5 - timeout-minutes: 10 diff --git a/.github/workflows/dependency-cooldown-audit.yml b/.github/workflows/dependency-cooldown-audit.yml deleted file mode 100644 index 3c7f247..0000000 --- a/.github/workflows/dependency-cooldown-audit.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Dependency cooldown audit - -on: - schedule: - - cron: '0 6 * * 1' - workflow_dispatch: - -permissions: - contents: read - -jobs: - audit: - uses: Quantus-Network/shared-workflows/.github/workflows/dependency-cooldown-audit.yml@v1 diff --git a/.github/workflows/dependency-cooldown.yml b/.github/workflows/dependency-cooldown.yml deleted file mode 100644 index a349f63..0000000 --- a/.github/workflows/dependency-cooldown.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Dependency cooldown - -on: - pull_request: - # labeled/unlabeled/edited are required so the gate re-runs when an - # emergency bypass label or reason is added. - types: [opened, synchronize, reopened, labeled, unlabeled, edited] - -permissions: - contents: read - -jobs: - dependency-cooldown: - uses: Quantus-Network/shared-workflows/.github/workflows/dependency-cooldown.yml@v1 diff --git a/.github/workflows/release-proposal.yml b/.github/workflows/release-proposal.yml deleted file mode 100644 index 07aeed4..0000000 --- a/.github/workflows/release-proposal.yml +++ /dev/null @@ -1,199 +0,0 @@ ---- -name: Release Proposal - -env: - CARGO_TERM_COLOR: always - -on: - workflow_dispatch: - inputs: - target_branch: - description: "Target branch for the PR (default: main)" - required: false - type: string - default: "main" - version_type: - description: "Type of version bump" - required: true - default: "patch" - type: choice - options: - - patch - - minor - - major - - custom - custom_version: - description: 'Custom version string (e.g., 1.2.3). Only used if version_type is "custom".' - required: false - is_draft: - description: "Is this a draft release?" - required: true - type: boolean - default: false - -jobs: - calculate-next-version: - name: ๐Ÿงฎ Calculate Next Version - runs-on: ubuntu-latest - outputs: - new_version: ${{ steps.versioner.outputs.new_version }} - new_tag: ${{ steps.versioner.outputs.new_tag }} - commit_sha_short: ${{ steps.vars.outputs.commit_sha_short }} - source_branch: ${{ steps.vars.outputs.source_branch }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - fetch-tags: true - - - name: Get current branch and commit SHA - id: vars - run: | - echo "commit_sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - echo "source_branch=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT - - - name: Get latest tag - id: latest_tag - run: | - # Get all version tags and sort them by version - latest_semver_tag=$(git tag -l "v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n 1) - - # If no tags found, use default - if [ -z "$latest_semver_tag" ]; then - latest_semver_tag="v0.0.0" - fi - - echo "latest_tag_found=$latest_semver_tag" >> $GITHUB_OUTPUT - echo "Latest semantic version tag found: $latest_semver_tag" - - - name: Calculate new version - id: versioner - env: - LATEST_TAG: ${{ steps.latest_tag.outputs.latest_tag_found }} - VERSION_TYPE: ${{ github.event.inputs.version_type }} - CUSTOM_VERSION: ${{ github.event.inputs.custom_version }} - run: | - # Remove 'v' prefix and any suffix for processing - current_version=${LATEST_TAG#v} - # Remove any suffix after the version number - current_version=$(echo "$current_version" | sed -E 's/-[^-]+$//') - - if [[ "$VERSION_TYPE" == "custom" ]]; then - if [[ -z "$CUSTOM_VERSION" ]]; then - echo "Error: Custom version is selected but no custom_version string provided." - exit 1 - fi - if [[ ! "$CUSTOM_VERSION" =~ ^v ]]; then - echo "Error: Custom version string MUST start with 'v' (e.g., v1.2.3)." - exit 1 - fi - new_version="$CUSTOM_VERSION" - else - # Split version and pre-release part - IFS='-' read -r version_core prerelease_part <<< "$current_version" - IFS='.' read -r major minor patch <<< "$version_core" - - # Increment based on type - if [[ "$VERSION_TYPE" == "major" ]]; then - major=$((major + 1)) - minor=0 - patch=0 - elif [[ "$VERSION_TYPE" == "minor" ]]; then - minor=$((minor + 1)) - patch=0 - elif [[ "$VERSION_TYPE" == "patch" ]]; then - patch=$((patch + 1)) - else - echo "Error: Invalid version_type: $VERSION_TYPE" - exit 1 - fi - new_version="v$major.$minor.$patch" - fi - - echo "New version: $new_version" - echo "new_version=$new_version" >> $GITHUB_OUTPUT - echo "new_tag=$new_version" >> $GITHUB_OUTPUT - - update-cargo-toml: - name: ๐Ÿ“ Update version files - needs: calculate-next-version - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - - - name: Create version bump branch and PR - env: - NEW_VERSION: ${{ needs.calculate-next-version.outputs.new_version }} - NEW_TAG: ${{ needs.calculate-next-version.outputs.new_tag }} - GITHUB_TOKEN: ${{ secrets.ADMIN_PAT }} - SOURCE_BRANCH: ${{ needs.calculate-next-version.outputs.source_branch }} - TARGET_BRANCH: ${{ github.event.inputs.target_branch }} - run: | - set -ex - new_cargo_version=${NEW_VERSION#v} - branch_name="release/${NEW_VERSION}" - - # Create new branch from source branch - git checkout "$SOURCE_BRANCH" - git checkout -b "$branch_name" - - # Update version in workspace Cargo.toml (safer than cargo set-version) - echo "Updating workspace Cargo.toml to version: $new_cargo_version" - sed -i -E "s/^version\s*=\s*\"[0-9a-zA-Z.-]+\"/version = \"$new_cargo_version\"/" Cargo.toml - - # Regenerate Cargo.lock with precise updates for our packages only - cargo update -p miner-cli --precise "$new_cargo_version" - cargo update -p miner-service --precise "$new_cargo_version" - cargo update -p pow-core --precise "$new_cargo_version" - cargo update -p metrics --precise "$new_cargo_version" - cargo update -p miner-telemetry --precise "$new_cargo_version" - cargo update -p engine-cpu --precise "$new_cargo_version" - cargo update -p engine-gpu --precise "$new_cargo_version" - - # Verify everything compiles correctly - cargo check --workspace - - # Commit changes - git config user.name "${{ github.actor }}" - git config user.email "${{ github.actor }}@users.noreply.github.com" - - git add Cargo.toml Cargo.lock - git commit -m "bump version to $NEW_VERSION" - git push origin "$branch_name" - - # Prepare PR title and body - PR_TITLE="Release $NEW_VERSION" - PR_BODY="Automated version bump for release $NEW_VERSION. - - ## Overview - - Version bump: ${{ github.event.inputs.version_type }} - - Type: ${{ github.event.inputs.version_type }} - - Draft: ${{ github.event.inputs.is_draft }} - - ## What changed - - Updated version in Cargo.toml and Cargo.lock - - Triggered by workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - - # Prepare labels - PR_LABELS="release-proposal" - if [[ "${{ github.event.inputs.is_draft }}" == "true" ]]; then - PR_LABELS="$PR_LABELS,draft-release" - fi - - gh pr create \ - --title "$PR_TITLE" \ - --body "$PR_BODY" \ - --base "$TARGET_BRANCH" \ - --head "$branch_name" \ - --label "$PR_LABELS" diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml deleted file mode 100644 index 9882fa8..0000000 --- a/.github/workflows/release-publish.yml +++ /dev/null @@ -1,157 +0,0 @@ ---- -name: Release Publish - -on: - pull_request: - types: [closed] - branches: - - main - -permissions: - contents: write - packages: write - -env: - CARGO_TERM_COLOR: always - -jobs: - create-tag: - name: Create Tag - if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release-proposal') - runs-on: ubuntu-latest - outputs: - version: ${{ steps.extract_version.outputs.version }} - tag: ${{ steps.extract_version.outputs.tag }} - is_draft: ${{ steps.extract_version.outputs.is_draft }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Extract version from PR title - id: extract_version - run: | - # Extract version from PR title (format: "Release vX.Y.Z") - VERSION_TAG=$(echo "${{ github.event.pull_request.title }}" | grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+') - if [ -z "$VERSION_TAG" ]; then - echo "Error: Could not extract version from PR title: ${{ github.event.pull_request.title }}" - exit 1 - fi - - VERSION=${VERSION_TAG#v} - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "tag=$VERSION_TAG" >> $GITHUB_OUTPUT - - # Check if this is a draft release - if [[ "${{ contains(github.event.pull_request.labels.*.name, 'draft-release') }}" == "true" ]]; then - echo "is_draft=true" >> $GITHUB_OUTPUT - else - echo "is_draft=false" >> $GITHUB_OUTPUT - fi - - echo "Extracted version: $VERSION" - echo "Extracted tag: $VERSION_TAG" - - - name: Create and push tag - run: | - git config user.name "${{ github.actor }}" - git config user.email "${{ github.actor }}@users.noreply.github.com" - git tag -a "${{ steps.extract_version.outputs.tag }}" -m "Release ${{ steps.extract_version.outputs.tag }}" - git push origin "${{ steps.extract_version.outputs.tag }}" - - build: - needs: create-tag - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - # Linux builds - - os: ubuntu-latest - target: x86_64-unknown-linux-gnu - binary_name: quantus-miner - asset_name: quantus-miner-linux-x86_64 - # Windows builds - - os: windows-latest - target: x86_64-pc-windows-msvc - binary_name: quantus-miner.exe - asset_name: quantus-miner-windows-x86_64.exe - # macOS builds (Intel) - - os: macos-15-intel - target: x86_64-apple-darwin - binary_name: quantus-miner - asset_name: quantus-miner-macos-x86_64 - # macOS builds (Apple Silicon) - - os: macos-latest - target: aarch64-apple-darwin - binary_name: quantus-miner - asset_name: quantus-miner-macos-aarch64 - - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ needs.create-tag.outputs.tag }} - - - name: setup rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - target: ${{ matrix.target }} - - - name: build binary - shell: bash - run: | - if [[ -n "${{ matrix.features }}" ]]; then - cargo build --release --locked --target ${{ matrix.target }} --features ${{ matrix.features }} - else - cargo build --release --locked --target ${{ matrix.target }} - fi - - - name: prepare binary - shell: bash - run: | - cd target/${{ matrix.target }}/release - if [[ "${{ matrix.os }}" == "windows-latest" ]]; then - cp ${{ matrix.binary_name }} ${{ matrix.asset_name }} - else - cp ${{ matrix.binary_name }} ${{ matrix.asset_name }} - strip ${{ matrix.asset_name }} - fi - - - name: upload binary artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.asset_name }} - path: target/${{ matrix.target }}/release/${{ matrix.asset_name }} - - release: - needs: [create-tag, build] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ needs.create-tag.outputs.tag }} - - - name: download all artifacts - uses: actions/download-artifact@v4 - with: - path: artifacts - - - name: create release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG: ${{ needs.create-tag.outputs.tag }} - IS_DRAFT: ${{ needs.create-tag.outputs.is_draft }} - run: | - # Prepare draft flag - if [[ "$IS_DRAFT" == "true" ]]; then - DRAFT_FLAG="--draft" - else - DRAFT_FLAG="" - fi - - # Create release with all artifacts - gh release create "$TAG" \ - --title "Release $TAG" \ - --generate-notes \ - $DRAFT_FLAG \ - artifacts/*/quantus-miner-* diff --git a/crates/miner-cli/build.rs b/crates/miner-cli/build.rs new file mode 100644 index 0000000..15d18c0 --- /dev/null +++ b/crates/miner-cli/build.rs @@ -0,0 +1,51 @@ +// lair: embed the git commit in the binary so `--version` and the build-info +// metric identify a deployed build by commit, not by the workspace semver +// (which does not change between commits on a branch that deploys on push). +// +// Resolution order: +// 1. MINER_BUILD_SHA in the environment (CI sets it from the checked-out ref) +// 2. `git rev-parse HEAD` of the workspace, with "-dirty" if the tree differs +// 3. "unknown" +use std::process::Command; + +fn git(args: &[&str]) -> Option { + let out = Command::new("git").args(args).output().ok()?; + if !out.status.success() { + return None; + } + let s = String::from_utf8(out.stdout).ok()?; + let s = s.trim(); + if s.is_empty() { + None + } else { + Some(s.to_string()) + } +} + +fn main() { + println!("cargo:rerun-if-env-changed=MINER_BUILD_SHA"); + + let sha = match std::env::var("MINER_BUILD_SHA") { + Ok(s) if !s.trim().is_empty() => s.trim().to_string(), + _ => match git(&["rev-parse", "--short=12", "HEAD"]) { + Some(head) => { + // Re-run when HEAD moves so a rebuild after a commit picks it up. + if let Some(dir) = git(&["rev-parse", "--git-dir"]) { + println!("cargo:rerun-if-changed={dir}/HEAD"); + println!("cargo:rerun-if-changed={dir}/refs/heads"); + } + let dirty = git(&["status", "--porcelain", "--untracked-files=no"]) + .map(|s| !s.is_empty()) + .unwrap_or(false); + if dirty { + format!("{head}-dirty") + } else { + head + } + } + None => "unknown".to_string(), + }, + }; + + println!("cargo:rustc-env=MINER_BUILD_SHA={sha}"); +} diff --git a/crates/miner-cli/src/main.rs b/crates/miner-cli/src/main.rs index 6b137c7..c8e9ffd 100644 --- a/crates/miner-cli/src/main.rs +++ b/crates/miner-cli/src/main.rs @@ -124,9 +124,18 @@ enum Command { }, } +// lair: semver plus the commit the binary was built from (see build.rs), so a +// deploy can assert the running binary is the commit it shipped. +const VERSION: &str = concat!( + env!("CARGO_PKG_VERSION"), + " (", + env!("MINER_BUILD_SHA"), + ")" +); + /// Quantus External Miner CLI #[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] +#[command(author, version = VERSION, about, long_about = None)] struct Args { #[command(subcommand)] command: Option,